[Home]Boost.Interfaces

BOOST WIKI | RecentChanges | Preferences | Page List | Links List

Showing revision 55

Overview of Boost Interfaces

Posted 29 Nov, 2004 by Jonathan Turkanis

Boost.Interfaces provides a macro-based interface definition language (IDL) which can be used to construct interfaces, as described in the September 2004 CUJ Article by Christopher Diggins. Interfaces are C++ class types associated with a set of named function signatures. An interface instance is a lightweight object which can be bound at runtime to any object which implements the interface, i.e., to any object of a type with non-static member functions having the same name and signature as those of the interface. Current applications include:

Future Applications may include:

I. Description of the IDL

In pseudo code, an interface definition looks like this (expressions in brackets are optional):

     [ template<typename T1, ..., typename Tm> ]
     interface i
        [ : i1, ... , in ] // base interfaces
     {
        [ zero or more member function declarations, just as in a
          C++ class, possibly involving the template parameters
          T1, ..., Tn if they are present ]
     };

Examples:

     interface IBar {
        void foo();
        int foo(const char*) const;
     };

     template<typename T>
     interface IBar2 : IBar {
        void goo(const T&);
     };

The psuedocode is translated into the Boost IDL as follows. Non-parameterized interfaces look like this:

     BOOST_IDL_BEGIN(name)
     [ BOOST_IDL_EXTENDS(base1)
       ...
       BOOST_IDL_EXTENDS(basen) ]
     [ function declarations ]
     BOOST_IDL_END(name)

Parameterized interfaces look like this:

    template<typename T1, ..., typename Tm>
    BOOST_IDL_BEGIN_TEMPLATE(name, m)
    [ BOOST_IDL_EXTENDS(base1)
       ...
       BOOST_IDL_EXTENDS(basen) ]
    [ function declarations ]
    BOOST_IDL_END_TEMPLATE(name, m)

Function declarations use the macros BOOST_IDL_FNn and BOOST_IDL_CONST_FNn, where n represents the arity and must appear at the end of the macro name as a numeral, e.g., BOOST_IDL_FN2. The first macro parameter is the name of the function, the second is the return type, and the subsequent parameters are the argument types. If named parameters are enabled (for Aspect Oriented Programming, which is not yet well-supported), then each parameter should be a Boost.PP tuple of the form (type, name). In previous versions, named parameters were enabled by default. In the most recent version (not yet available), you have to #define BOOST_IDL_ENABLE_ADVICE if you want to use named parameters.

For example, the pseudocode interface definition

     interface IBar {
        void foo();
        int foo(const char*) const;
     };

is rendered in the Boost IDL as follows:

     BOOST_IDL_BEGIN(IBar)
     BOOST_IDL_FN0(foo, void)
     BOOST_IDL_CONST_FN1(foo, int, const char*)
     BOOST_IDL_END(IBar)

Similarly, the pseudocode definition

     template<typename T>
     interface IBar2 : IBar {
        void goo(const T&);
     };

is rendered as:

     template<typename T>
     BOOST_IDL_BEGIN_TEMPLATE(IBar, 1)
     BOOST_IDL_EXTENDS(IBar)
     BOOST_IDL_FN1(goo, void, const T&)
     BOOST_IDL_END_TEMPLATE(IBar, 1)

II. Use of Interface Instances

Interfaces are lightweight value types, with the following layout (assuming EBO is applied, which it is on all the suppored platforms):

    struct {
        const void* pv;
        const void* table;
    };

An interface instance can be bound to any object of a type which implements the interface. Theoretically an exact match is required for all member function signatures, but this is not currently enforced. Objects can be bound to an interface instance upon constuction of the interface or using assignment. Objects can be unbound by binding another object or by assigning 0 to the interface instance. A Member function of the bound object having the same name and signature as a function declared in the interface definition can be invoked directly through the interface instance using the dot operator. The lifetime of the bound object is not managed by the interface instance — for that, you need to use the smart pointers or smart references described later.

Examples:

   struct CBar1 {
       void foo() { std::cout << "CBar1::foo()\n"; }
       int foo(const char*)
       {
          std::cout << "CBar1::foo(const char*)\n";
          return 0;
       }
   };

   struct CBar2 {
       void foo() { std::cout << "CBar2::foo()\n"; }
       int foo(const char*)
       {
          std::cout << "CBar2::foo(const char*)\n";
          return 0;
       }
   };

    int main()
    {
        CBar1 b1;
        CBar2 b2;
        IBar ib = b1;
        ib.foo(); // prints "CBar1::foo()"
        assert(ib);

        ib = b2;
        ib.foo(); // prints "CBar2::foo()"

        ib = 0;
        assert(!ib);
        ib.foo();  // undefined behavior.

        const Bar1 b3;
        ib = b3; // Won't compile.
   }

III. const-correctness

If you want to restrict the member functions which can be invoked through an interface instance to those which are declared const, you should use a const view of the interface:

     CBar1 b1;
     const CBar1 b2 = CBar1();
     const_interface<IBar> ib = b1; // Okay
     ib = b2; // Also okay.
     ib.foo("hello");
     ib.foo(); // Error: void IBar::foo() is non-const.

(The reason that const views have to be used is that during construction const-volatile semantics don't apply. As a result, if certain key use-cases of const interfaces were supported by relying on native const-ness, violations of const correctness would also be possible.)

IV. Fixed Views.

To create an interface instance which can't be rebound, use a fixed_interface:

   CBar1 b1;
   CBar1 b2;
   fixed_interface<IBar> ib = b1;
   ib = b2; // error
   ib = 0;  // error.

Fixed views are the element_types of smart interface pointers and references. They are used to prevent resource leaks and double deletes which might otherwise occur if you could dereference a smart interface pointer and then bind it to a new object using operator=.

V. Smart Pointers.

There are two smart interface pointer templates: unique_iptr and shared_iptr.

unique_iptr

unique_iptr has essentially the same interface and semantics as static_move_ptr from

   http://home.comcast.net/~jturkanis/move_ptr/

The difference are:

  1. the element_type of unique_iptr<I> is fixed_interface<I>
  2. deletion policies are not supported
  3. release() is not supported, since a released object cannot be destroyed safely (too much type information has been lost at the time of binding)

shared_iptr

shared_iptr has essentially the same interface and semantics as boost::shared_ptr. The differences are

  1. the element_type of unique_iptr<I> is fixed_interface<I>

Example:

   shared_iptr<IBar> ptr(new CBar1);
   ptr->foo(); // prints "CBar1::foo()"

The last released version required the interface type to be complete before you could instantiate a smart pointer template. This will no longer be true in the new version.

V. Smart References.

There are two smart reference templates: unique_obj and shared_obj. Each is essentially the same as the corresponding smart pointer template except

  1. interface member functions are accessed using the dot operator instead of operator->.
  2. the functions reset(), unique() and use_count() are implemented as non-members to prevent collision with interface members with the same names.

Example:

    int main()
    {
        shared_obj<IBar> obj(new CBar1);
        obj.foo(); // prints "CBar1::foo()"

        reset(obj, new CBar2);
        obj.foo(); // prints "CBar2::foo()"
    } 

Smart reference types smart_ref<I> have to derive from fixed_interface<I> to allow access using the dot operator, and so require that the interface type be complete.

VI. Automated Delegation ("Dynamic Inheritance")

Boost.Interfaces allows you to define classes which implement an interface by delegating to a contained object specified at runtime:

    class DelegatingIBar
        : delegating<IBar>
    {
    public:
        // friend class access; // See below.

        DelegatingIBar()
        {
            delegating<IBar>::set(&b1);
        }
        void switch_delegate()
        {
            delegating<IBar>::set(&b2);
        }
    private:
        CBar1 b1;
        CBar2 b2;
    }

    int main()
    {
        DelegatingIBar d;
        d.foo(); // prints "CBar1::foo()"
        d.switch_delegate();
        d.foo(); // prints "CBar2::foo()"

        CBar1 b;
        set_delegate(d, &b); // error
    }

The last line ceases to be an error if the friend declaration in DelegatingIBar is uncommented.

Delegate lifetimes are not managed.

VII. Name Hiding

Under the current implementation, if two base interfaces contain members of the same name and sufficiently similar signatures, attempting to call that member through a derived interface instance results in an ambiguity error. This is desirable behavior, because there is no non-arbitrary way to resolve the ambiguity. Unfortunately, since most compilers do not implement EBO with multiple inheritance, I have reimplemented the library using single inheritance to give all interfaces the binary layout shown above. The result is that certain function invocations which should be ambigous will now be resolved according to the order of the base interfaces in the interface definition.

VIII. Source Organization

All the templates mentioned are in the namespace boost::interfaces. The core IDL is accessed by including <boost/interface.hpp>. const_interface, fixed_interface and the smart pointer and references each have their own headers in the directory <boost/interfaces>. The class template delegating and the function templates get_delegate() and set_delegate() are in the header <boost/interfaces/delegation.hpp>.

[buy lipitor online] [buy lipitor] [[buy lipitor online]]


BOOST WIKI | RecentChanges | Preferences | Page List | Links List
Edit revision 55 of this page | View other revisions | View current revision
Edited August 7, 2008 12:39 pm (diff)
Search:
Disclaimer: This site not officially maintained by Boost Developers