C++ Boost Interface Library ( BIL )

Preliminary Documentation for Boost Pre-submission Evaluation

by Jonathan Turkanis

documentation by Christopher Diggins

Updated November 5, 2004


Synopsis
Introduction
Interface Declarations and References
Interface Semantics
Interface Inheritance
Parameterized Interfaces
Comparing Interfaces to Abstract Base Classes
Const Interfaces
Interface Smart Pointers
Metafunctions and Concept Checking
Delegations
Aspect Oriented Programming
Case Study: Invariants
Case Study: Collections
Case Study: RTTI
Under the Hood: How the BIL Macros Work
Future Directions
Acknowledgements
Bibliography
Footnotes
License

Synopsis

The C++ Boost Interface Library ( BIL ) is a library by Jonathan Turkanis which is intended for public review for eventual submission to the Boost C++ library.

Disclaimer: The BIL is not a Boost library.


Introduction

The C++ Boost Interace Library ( BIL ) provides a means to express formal interfaces as a set of function signature through macros. With the declaration of formal interfaces the BIL also provides the following additional functionality:

Interface Reference Types

The primary role of the BIL is to generate interface reference types which are compatible with any object which implements an interface. An object is said to implement a particular interface when it provides public implementations for the complete set of functions of the interface with matching signatures.

Interface reference variables behave like plain C++ references which may be bound to any object which implements the corresponding formal interface. Interface reference variables provide access to all of the functions in the formal interface using dot notation like a C++ reference. Unlike plain C++ references, interface reference variables may be rebound, and may also refer to null.

The interface reference types generated by the BIL provide unobstrusive implicit polymorphism. This is in contrast to interface constructs in languages like Java and C# which require that a class must declare its intention to implement an interface. One of the more noteworthy implications of this approach is that unlike Java / C# the functions make up a formal signature are not neccessarily virtual.

Aspect Oriented Programming

The basis of Aspect Oriented Programming (AOP) is the expression of crosscutting concerns. A crosscutting software concern is a responsibility or role which crosses the boundaries of modular divisions. In otherwords a crosscutting concern can not be cleanly expressed in an object oriented design. The BIL provides a method for expressing crosscutting concerns.

Concept Checking

The BIL provide a straightfoward method to assure that template arguments implement a set of member functions. This compliments other concept checking techniques.

Delegation

Delegation is the technique of forwarding implementations of functions to a member field. The BIL provides a method to forward the implementation of an entire set of functions to a single member field.

top


Interface References and Declarations

The most useful functionality of the BIL is the ability to define reassignable interface reference variables. In order to use interface reference variables we have to first declare a formal interface.

Declaring a Formal Interface

Here us an example of a formal interface in pseudo-code:
Listing 1)
  interface IShape {
    int GetX();
    int GetY();
    float GetArea();
    void SetXY(int x, int y);
    void OffSetXY(int x, int y);
    const char* GetName();
  };
Using macros we can declare the formal interface as follows:
Listing 2)
  BOOST_INTERFACE_BEGIN(IShape)
    BOOST_INTERFACE_CONST_FUNCTION0(GetX, int)
    BOOST_INTERFACE_CONST_FUNCTION0(GetY, int)
    BOOST_INTERFACE_CONST_FUNCTION0(GetArea, double)
    BOOST_INTERFACE_FUNCTION2(SetXY, void, (int, x), (int, y))
    BOOST_INTERFACE_FUNCTION2(OffSetXY, void, (int, x), (int, y))
    BOOST_INTERFACE_CONST_FUNCTION0(GetName, const char*)
  BOOST_INTERFACE_END(IShape)
Notice that:

Interface Reference Variables

One of the advantages of the macros is that provide for us an interface reference type that is compatible with any object that implicitly implements the formal interface. Consider the following two classes:
Listing 3)
  class Circle {
    int mx;
    int my;
    int mr;
  public:
    int GetX() { return mx; }
    int GetY() { return my; }
    void SetXY(int x, int y) { mx = x; my = y; }
    void OffsetXY(int x, int y) { SetXY(GetX() + x, GetY() + y); }
    const char* GetName() { return "circle"; }
    int GetRadius() { return mr; }
    double GetArea() { return 3.14159 * mr * mr; }
  };

  class Rectangle {
    int mx0;
    int my0;
    int mx1;
    int my1;
  public:
    int GetX() { return (mx0 + mx1) / 2; }
    int GetY() { return (my0 + my1) / 2; }
    void SetXY(int x, int y) { OffsetXY(x - GetX(), y - GetY()); }
    void OffsetXY(int x, int y) { mx0 += x; my0 += y; mx1 += x; my1 += y; }
    const char* GetName() { return "rectangle"; }
    int GetHeight() { return my1 - my0; }
    int GetWidth() { return mx1 - mx0; }
    int GetArea() { return GetHeight() * GetWidth(); }
  };
We can use the a reference generated by the interface declaration macros for IShape, shown previously in listing 2, as follows:
Listing 4)
  Circle c;
  Rectangle r;
  IShape i(c); // construct an IShape reference which is initialized to refer to c
  puts(i.GetName()); // outputs "circle"
  i = s; // reassign the IShape reference to refer to s
  puts(i.GetName()); // outputs "rectangle"
  i = null;

top


Interface Semantics

Interface Method Binding, Virtual and Non-Virtual

When assigning an object to an interface reference (directly or through another interface reference) the interface reference binds directly to that object's methods. If a particular method is virtual, then the function still binds to the most derived version.
Listing 13)
  BOOST_INTERFACE_BEGIN(IFuBar)
    BOOST_INTERFACE_FUNCTION1(Fu, void)
    BOOST_INTERFACE_FUNCTION1(Bar, void)
  BOOST_INTERFACE_END(IFuBar)

  struct Base {
    void Fu() { printf("base fu\n"); }
    virtual void Bar() { printf("base bar\n"); }
  }

  struct Derived : public Base
    void Fu() { printf("derived fu\n"); }
    virtual void Bar() { printf("derived bar\n"); }
  }

  int main() {
    Derived d;
    Base& b = d;
    IFuBar i;
    i = b;
    i.Fu(); // outputs "base fubar"
    i.Bar(); // outputs "derived bar"
    return 0;
  }

top


Interface Inheritance

An interface can be defined which inherits from one or more other interfaces. For instance we could break up the earlier example of IShape from listing 1 into several separate formal interfaces.

Listing 5)
  interface IObject {
    const char* GetName();
  };

  interface IPosition {
    int GetX();
    int GetY();
  };

  interface IMoveable {
    void SetXY(int x, int y);
    void OffSetXY(int x, int y);
  };

  interface IShape : IObject, IPosition, IMoveable {
    float GetArea();
  };

  interface IRectangle : IShape {
    int GetHeight();
    int GetWidth();
  };

  interface ICircle : IShape {
    int GetRadius();
  };
  
The interface library provides special macros for defining sub-interfaces. The formal interfaces described in listing 6 could be declared as follows:
Listing 6)
  BOOST_INTERFACE_BEGIN(IObject)
    BOOST_INTERFACE_CONST_FUNCTION0(GetName, const char*)
  BOOST_INTERFACE_END(IObject)

  BOOST_INTERFACE_BEGIN(IShapeSpecific)
    BOOST_INTERFACE_CONST_FUNCTION0(GetArea, double)
  BOOST_INTERFACE_END(IShapeSpecific)

  BOOST_INTERFACE_BEGIN(ICircleSpecific)
    BOOST_INTERFACE_CONST_FUNCTION0(GetRadius, int)
  BOOST_INTERFACE_END(ICircleSpecific)

  BOOST_INTERFACE_BEGIN(IRectangleSpecific)
    BOOST_INTERFACE_CONST_FUNCTION0(GetHeight, int)
    BOOST_INTERFACE_CONST_FUNCTION0(GetWidth, int)
  BOOST_INTERFACE_END(IRectangleSpecific)

  BOOST_INTERFACE_BEGIN(IPosition)
    BOOST_INTERFACE_CONST_FUNCTION0(GetX, int)
    BOOST_INTERFACE_CONST_FUNCTION0(GetY, int)
  BOOST_INTERFACE_END(IPosition)

  BOOST_INTERFACE_BEGIN(IMoveable)
    BOOST_INTERFACE_FUNCTION2(SetXY, void, (int, x), (int, y))
    BOOST_INTERFACE_FUNCTION2(OffSetXY, void, (int, x), (int, y))
  BOOST_INTERFACE_END(IMoveable)

  BOOST_INTERFACE_BEGIN(IShapeSpecific)
    BOOST_INTERFACE_CONST_FUNCTION0(GetName, const char*)
  BOOST_INTERFACE_END(IShapeSpecific)

  BOOST_SUBINTERFACE_BEGIN(IShape)
    BOOST_SUPERINTERFACE(IShapeSpecific)
    BOOST_SUPERINTERFACE(IPosition)
    BOOST_SUPERINTERFACE(IMoveable)
    BOOST_SUPERINTERFACE(IObject)
  BOOST_SUBINTERFACE_END(IShape)

  BOOST_SUBINTERFACE_BEGIN(ICircle)
    BOOST_SUPERINTERFACE(ICircleSpecific)
    BOOST_SUPERINTERFACE(IShape)
  BOOST_SUBINTERFACE_END(ICircle)

  BOOST_SUBINTERFACE_BEGIN(IRectangle)
    BOOST_SUPERINTERFACE(IRectangleSpecific)
    BOOST_SUPERINTERFACE(IShape)
  BOOST_SUBINTERFACE_END(IRectangle)
Note: the BIL at this moment does not allow the introduction of new functions into a subinterface, this functionality is planned for a next versions.

top


Parameterization

Boost interfaces can be parameterized but require special interface begin and end macros that include the number of template parameters.
Listing 8)
  template<typename T>
  BOOST_INTERFACE_TEMPLATE_BEGIN(IArray)
    BOOST_INTERFACE_FUNCTION1(GetAt, T, (int, i))
    BOOST_INTERFACE_FUNCTION1(SetAt, void, (int, i), (T, x))
  BOOST_INTERFACE_TEMPLATE_END(IArray)
This can now be used like any other parameterized type, i.e.:
Listing 9)
  template<type T>
  struct Array {
    T GetAt(int i) {
      return m[i];
    }
    void SetAt(int i, T x) {
      m[i] = x;
    }
    std::vector<t> m;
  }

  Array<int> a;
  IArray<int> i = a;

top


Comparing to Abstract Base Classes

The common idiom in C++ to emulate formal interfaces is to use abstract base classes. There are several disadvantages to this approach: When using interfaces with multiple interfaces, it becomes interesting to compare the memory usage and execution times of an interface reference with regards to the common C++ idiom of emulating interfaces through abstract base classes. If we emulate the formal interfaces of listing 5 using virtual functions then we would have the following:
Listing 10)
  struct AbcObject {
    virtual const char* GetName() = 0;
  };

  struct AbcPosition {
    virtual int GetX() = 0;
    virtual int GetY() = 0;
  };

  struct AbcMoveable {
    virtual void SetXY(int x, int y) = 0;
    virtual void OffSetXY(int x, int y) = 0;
  };

  struct AbcShape : public AbcObject, public AbcPosition, public AbcMoveable {
    virtual float GetArea() = 0;
  };

  struct AbcRectangle : public AbcShape {
    virtual int GetHeight() = 0;
    virtual int GetWidth() = 0;
  };

  struct AbcCircle : public AbcShape {
    virtual int GetRadius() = 0;
  };
Now if we rewrote the Circle and Rectangle class from listing 4 to inherit from the abstract base classes of listing 10 we end up with objects that take up considerably more space. When compiled on most 32 bit platform, both the Circle and Rectangle object take up 12 more bytes, 4 bytes for each neccessary vtable. The interface references are consistently 8 bytes, but of course are only used when the polymorphism is required. It is conceivable that in a large piece of software, the times when run-time polymorphism is actually needed is very rare, we only pay for polymorphism when we need it.

Another disadvantage of ABC's is that most compilers are unable to inline the function calls within the classes inheriting from an abstract base class because they are declared as virtual. The actual performance penalties vary greatly from platform to platform and compiler to compiler so we leave it as an exercise for the reader to verify the speed of execution for themselves.

top


Const Interfaces

You can declare an interface variable with const semantics using the const_interface parameterized type as a wrapper. A const_interface variable can be assigned to any object, but only makes available the const functions of the object,
Listing 11)
  Bar b;
  const_interface<IBar> ib = b;

top


Smart Pointers

The BIL defines two smart interface pointer types, shared_interface_ptr and unique_interface_ptr. The unique_interface_ptr behaves like a boost::scoped_ptr, and can be used as follows:

Listing 12)
    {
      Bar b;
      unique_interface_pointer<IBar> p(new Bar);
      *p = b; // Error: *p is "fixed."
    } // the object referred to by p is automatically destroyed

The unique_interface_ptr specifically does not provide a release function as might first be expected.

The shared_interface_ptr is a smart pointer modelled on boost::shared_ptr. The object referred to is deleted when the last shared_interface_ptr is destroyed. Currently no weak_interface_ptr is available.

top


Metafunctions and Concept Checking

The BIL supplies several compile-time functions which can be useful for concept checking of template arguments.

The is_interface<typename T>::value metafunction is true only when a passed type is in fact an interface

The extends<typename SUB_INTERFACE, typename SUPER_INTERFACE>::value metafunction is true only when the interface SUB_INTERFACE inherits from the inteface SUPER_INTERFACE.

The most useful metafunction is implements<typename T, typename INTERFACE>::value which is true only when the passed type T implements the formal interface INTERFACE.

top


Delegation

Delegation is a technique of forwarding the implementation of a particular interface to an object. The BIL provides a method to do this at run-time, which is sometimes referred to as 'dynamic inheritance'. Delegation can be used instead of inheritance to model object IS-A relationships without requiring an object to subtype another class.

The BIL provides a delegation mechanism through the set_delegate function:

  template<typename Interface, typename T, typename Delegate<
  void set_delegate(T& t, Delegate& d) {
    static_cast<Interface&>(t) = d;
  }

top


Aspect Oriented Programming

AOP ( aspect oriented programming ) support is provided by the Boost Interfaces library. AOP is the technique of expressing crosscutting concerns in a modular manner. A crosscutting concern is a concern which crosses boundaries of responsibility of code segments in a non-hierarchical manner. Some example of crosscutting concerns are: logging, thread/process priority, synchronization, error handling, invariant testing, etc.

Some commonly used terminology:

Disclaimer : This terminology has been derived from the AspectJ documentation and common usage. AspectJ is currently the industry defacto standard when referring to AOP. The authour views the use of the term "advice" to refer to the expression of a crosscutting concern as being deleterious to the spirit of reuse as it implies implementation details rather than a more abstract notion of a reusable crosscutting concern.

Pointcut

The difficulty in implementing AOP with C++ previously was the ability to express a pointcut in a simple and straightforward manner. Boost interfaces provide a convenient and straightforward way to express a pointcut.

Advice Class

The Boost Interfaces library advice class must model the following concept:
  struct Advice {
  template<...>
  void OnBefore(...);

  template<...>
  virtual void OnAfter(...);

  template<...>
  virtual bool OnProceedQuery(...);

  template<...>
  virtual void OnFinally(...) { };

  template<...>
  virtual void OnExcept(...);

  template<...>
  [unspecified] void TransformResult(...);
  };

Crosscutting

The Boost Interface library provides a crosscut macro, which redefines a type based on an application of an advice class at a specified pointcut (interface).

[TODO]

top


Case Study: Invariants

A well known example of crosscutting occurs when in a class we want to assure that an invariant condition is always satisfied. Invariant assertions are an integral part of Contract Programming, that previously are very difficult to do in C++. An invariant is commonly implemented as an assertion that is executed on entry and on exit of each function in a class. Consider the following example of a stream that must be open whenever something is written to it:

top


Case Study: RTTI

Run-time type information (RTTI) in C++ is not very efficient and support as a result is often only conditionally included in a particular build. Using interface variables and systematic inclusion of certain functions we can easily roll our own efficient RTTI mechanism.

For instance consider the inclusion in any particular class the following

  class my_class_name {
    static int GetTypeId() const { return MY_CLASS_ID; }
    static const char* GetTypeName() const { return "my_class_name"; }
  };
We then define an interface as follows:
  BOOST_INTERFACE_BEGIN(IUnknownType)
    BOOST_INTERFACE_CONST_FUNCTION0(GetTypeID, int)
    BOOST_INTERFACE_CONST_FUNCTION0(GetTypeName, const char*)
  BOOST_INTERFACE_END(IUnknownType)
We can now use the often discouraged but commonly seen idiom of run-time switching based on a type id:
  void TypeName(IUnknown i) {
    switch(i.GetTypeID())
    {
      case (MY_CLASS_ID) {
        std::cout << "my class :-)" << std::endl;
      }
      default {
        std::cout << "someone else's class :-(" << std::endl;
      }
    }
  }

top


Case Study: Collections

To demonstrate the various functionality provided in the library working together consider the following example, which demonstrates a type-safe, run-time polymorphic iterator:
  template<typename T>
  BOOST_INTERFACE_TEMPLATE_BEGIN(IIterator, 1)
    BOOST_INTERFACE_FUNCTION0(GotoNext, void)
    BOOST_INTERFACE_CONST_FUNCTION0(AtEnd, bool)
    BOOST_INTERFACE_FUNCTION0(Get, T)
    BOOST_INTERFACE_FUNCTION1(Set, void, (T,  x))
  BOOST_INTERFACE_TEMPLATE_END(IIterator, 1)

  template<typename T>
  BOOST_INTERFACE_TEMPLATE_BEGIN(ICollection, 1)
    BOOST_INTERFACE_FUNCTION0(GetIter, unique_interface_ptr<IIterator<T> >);
  BOOST_INTERFACE_TEMPLATE_END(ICollection, 1)

  template<typename T>
  BOOST_INTERFACE_TEMPLATE_BEGIN(IArray, 1)
    BOOST_INTERFACE_CONST_FUNCTION1(GetAt, T, (unsigned int, i))
    BOOST_INTERFACE_FUNCTION2(Set, void, (int, i), (T,  x))
    BOOST_INTERFACE_CONST_FUNCTION0(Count, int, (int, i))
  BOOST_INTERFACE_TEMPLATE_END(IArray, 1)

  template<typename T>
  BOOST_SUBINTERFACE_BEGIN(IArrayCollection)
    BOOST_SUPER_INTERFACE(IArray<T>)
    BOOST_SUPER_INTERFACE(ICollection<T>)
  BOOST_SUBINTERFACE_END(IArrayCollection)
We can use the interfaces above in the following manner:
  template<typename T>
  struct ArrayIter {
    ArrayIter(Array<T>& a) : mArray(a), mIndex(0) {}
    T Get() {
      return mArray.GetAt(mIndex);
    }
    void Set(T x) {
      mArray.SetAt(mIndex);
    }
    void GotoNext() {
      mIndex++;
    }
    bool AtEnd() {
      return mIndex >= mArray.Count();
    }
    Array<T>& mArray;
    int mIndex;
  };

  template<typename T>
  struct Array {
    T GetAt(int i) {
      return m[i];
    }
    void SetAt(int i, T x) {
      m[i] = x;
    }
    int Count() {
      return m.size();
    }
    unique_interface_ptr<IIterator<T> > GetIter() {
      return new ArrayIter<T>(this);
    }
    std::vector<t> m;
  };

  void OutputInts(IIterator<int> iter) {
    while (!iter.AtEnd()) {
      cout << iter.Get() << endl;
      iter.GotoNext();
    }
  }

  int main() {
    Array<int> a(10);
    IArrayCollection<int> x = a;
    for (int i(0); i < 10; i++) {
      x.SetAt(i, i);
    }
    unique_interface_ptr<IIterator<int> > iter(x.GetIter());
    OutputCollection(iter);
    return 0;
  }
The advantage of this new iterator over traditional STL iterators is that the type of the collection and iterator doesn't have to be known at run-time. For instance OutputInts could occur in a separate library. We can also make decisions at run-time as to which kind of collection would be more appropriate to use. The code for using this kind of collection and iterator is also much easier to write and read, especially for naive programmers.

top


Under the Hood: How the BIL Macros Work

[TODO]

top


Future Directions

[TODO]

top


Acknowledgements

The Boost Interfaces library was written by Jonathan Turkanis, with contributions by Christopher Diggins, David Abrahams and Paul Mensonides. The documentation was written by Christopher Diggins and Jonathan Turkanis.

top


Bibliography

  1. C++ with Interfaces, Christopher Diggins - C/C++ Users Journal, September 2004
  2. Aspect Oriented Programming in C++, Christopher Diggins - Doctor Dobbs Journal, August 2004
  3. Separation of Concerns, Walter Hürsch and Cristina Videira Lopes - Northeastern University technical report NU-CCS-95-03, Boston, February 1995.
  4. Proposal: Interfaces , post by Christopher Diggins on comp.std.c++ newsgroup, April 9th 2004
  5. C++ Support for Delegation, Lois Goldthwaite, Doc. No. SC22/WG21/N1363 J16/02/0021 8 May 2002.
  6. Concept Checking - A more abstract complement to type checking, Bjarne Stroustrup N1510=03-0093, October 22, 2003

top


License

The Boost Interfaces Library ( BIL ) is © copyright 2004 Jonathan Turkanis and is licensed under the Boost Software License, Version 1.0.

Disclaimer: The Boost Interfaces Library ( BIL ) is not a Boost library.


This document is © Copyright Christopher Diggins 2004.