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.
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:
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.
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.
The BIL provide a straightfoward method to assure that template arguments implement a set of member functions. This compliments other concept checking techniques.
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 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:
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:
- Every BOOST_INTERFACE_FUNCTION macro is trailed by a number indicating the arity (the number of parameters) of the function signature
- const function signatures have a separate macro
- the first parameter is the name of the function
- the paranthesis is required around each parameter type and parameter name pair
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:
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
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;
}
topAn 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.
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:
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
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.:
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
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.
Bar b; const_interface<IBar> ib = b;top
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:
{
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.
topThe 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
template<typename Interface, typename T, typename Delegate<
void set_delegate(T& t, Delegate& d) {
static_cast<Interface&>(t) = d;
}
topDisclaimer : 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.
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(...);
};
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
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
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.
topThe 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.