Introduction
The Object Oriented Template Library (OOTL), is a new open-source library which provides lightweight object-oriented alternatives to the C++ primitives, as well as the STL collections. This article introduces the theory and techniques which allow us to have objects that are both lightweight and run-time polymorphic, and we introduce the OOTL primitives.
Requirements
The OOTL requires the C++ Boost library version 1.32.0, which is freely available at www.boost.org, in order to work. The Boost Interfaces Library is not in fact part of Boost, but it is included with the source code for this article.
About The Source Code
The source code included with this article includes a pre-beta version of the Boost Interfaces Library, along with version 0.1 of the OOTL. There are several files named xxx-test.hpp which contain examples of usage of the OOTL. The best examples of usage of the BIL can be found in the files collections.hpp which define numerous interfaces as implemented by the various OOTL collection classes.
Background
C++ lacks a feature, found in many modern programming languages that particularly interests me: interfaces. An interface is viewed often as a formal expression of an abstract base class (ABC). Another view of an interface, which I feel is more intuitive, is that of simply a set of function signatures. Implementing an interface means that the set of function signatures represented by the interface are publicly accessible for any given object. These two views are similar but not identical.
ABC's are not Interfaces
If we accept that an interface is simply a set of function signatures, then there is one notable omission. There is no specification, explicit or implicit, that functions should be virtual
. An abstract base class, by comparison, explicitly requires every function to be virtual
.
This view is somewhat contrary to popular belief as evidenced by the implementation of interfaces, as ABC's in many recent programming languages, such as Java and C#.
Dynamic Dispatch
A virtual member function has the property that any call to that member function will be dispatched to the most derived version. This property is not desirable for an interface function. By removing this requirement, we can remove the vtable pointer needed inside of an object with virtual functions.
Interfaces and the VTable Penalty
Most C++ programmers are familiar with the virtual penalty. That is essentially to say that if you want run-time polymorphism, you end up with a vtable embedded in your project. On most 32-bit platforms, this means that every object requires an extra 32 bits of space, whether or not you actually use the polymorphism. At the same time, you also suffer an, albeit minor, performance hit during the dynamic dispatch. In other words, in order to have an object oriented hierarchy in C++ -- as well any other object-oriented languages for that matter -- you have to suffer the indignities of dynamic dispatch throughout your code.
We can, in theory, avoid the vtable penalty if we use interface reference variables when we want run-time polymorphism. The interface reference variable then can store the type information. This is where the BIL (Boost Interfaces Library) comes in.
The technique of using templates to generate dynamic function dispatch tables and storing static pointers in the interface pointer was first described in my article in the September, 2004 issue of the C/C++ Users Journal entitled Interfaces in C++. Jonathan Turkanis used this technique to build the BIL, which is included in its alpha-state, with the source code for this article.
About the BIL
The Boost Interfaces Library, is a very powerful macro library by Jonathan Turkanis, which is not yet officially beta. This means that the general feature set hasn't been completely frozen. It is as close to beta as it can be, so he gave me permission to include it with the OOTL release. Unfortunately, there is no up to date documentation at this point, but it isn't too hard to figure it out from the numerous case examples included in the code. (That having been said, an outdated version of the documentation can be found here, but remember, this documentation is incomplete and outdated.)
The IObject interface
The most interesting interface, is one that the OOTL primitives implement, and that allows us to have RTTI (Run-Time Type Information) without needing to enable C++ RTTI on our compiler.
Abstractly, the IObject
interface looks like:
interace IObject {
const char* GetClassName();
int GetClassId();
ObjectIdentity GetObjectId();
int GetObjectSize();
}
Unfortunately, this syntax is not possible in C++ -- you can write to your favorite C++ committee member asking them for that syntax -- but using the Boost Interface Library macros (called the IDL, for interface description language), the interface is expressed as follows:
BOOST_IDL_BEGIN(IObject)
BOOST_IDL_FN0(GetObjectSize, int)
BOOST_IDL_FN0(GetClassId, int)
BOOST_IDL_FN0(GetClassName, char const*)
BOOST_IDL_FN0(GetObjectId, ObjectId)
BOOST_IDL_END(IObject)
Having defined this interface, we can use the type IObject
to refer to any object which provides the required function signatures. For instance, consider the following class:
struct FuBar {
int GetObjectSize() { return sizeof(FuBar); }
int GetClassID() { return 1; }
char const* GetClassName() { return "fubar"; }
ObjectId GetObjectId() { return static_cast<ObjectID>(this); }
}
Notice that this class has no virtual functions, but it is run-time polymorphic using IObject
. The OOTL provides the following function, for testing purposes, in the file object-test.hpp:
void PrintObjectDetails(IObject o) {
printf("class = %s, size = %d, object-id = %p, class-id = %x\n",
o.GetClassName(), o.GetObjectSize(), o.GetObjectId(), o.GetClassId());
}
when used as follows:
FuBar baz;
PrintObjectDetails(baz);
outputs something similar to the following:
class = fubar, size = 1, object-id = 001FDE0, class-id = 1
The IObject
type behaves like a regular C++ reference to the value it is assigned to, except that it can be reassigned, and it can refer to any object that matches the function signatures of the interface it represents.
OOTL_DEF_OBJECT macro
Implementing IObject
for every class is very repetitive, and it requires careful management of class-IDs to guarantee that they are unique. Clearly that is not much fun, and being lazy -- an important characteristic of any good programmer -- I wrote the IObject
implementation code as a macro.
The OOTL_DEF_OBJECT
macro takes one parameter, the name of the object, and must occur anywhere within a publicly visible part of the declaration of a class. It then generates the necessary IObject
implementing code. This means that we can rewrite the FuBar example as:
struct FuBar {
OOTL_DEF_OBJECT(FuBar)
}
And it works exactly if we wrote it out long hand. The class ID is generated by the compiler and is guaranteed to be unique for every declaration of OOTL_DEF_OBJECT
within your project.
The OOTL Primitives
The OOTL provides the following primitive replacements: Int
, UInt
, Char
, Bool
, Float
, and Dbl
. The OOTL primitives all implement IObject
, and they provide implicit conversion from (but not to) their built-in counterparts. There is explicitly no implicit conversion to primitives because of the surprising side-effects that can occur, instead they all provide a ToPrimitive()
function. If this is not to the programmer's liking, by all means go in and change it. The OOTL primitives have another interesting characteristic, they are automatically initialized. Again, this may or may not be to a programmer's liking, and it is still relatively simple to make the necessary changes.
OOTL Primitives Compared to Naive OO Primitives
Many people hear about the OOTL primitives, and automatically assume that they are the same as the OO primitives that have come before, i.e. slow and bloated. Before the BIL, the only known way to define object oriented primitives was through abstract base classes. Something akin to the following:
struct AbcObject {
virtual int GetObjectSize() = 0;
virtual int GetClassID() = 0;
virtual char const* GetClassName() = 0;
virtual ObjectId GetObjectId() = 0;
};
struct NaiveInt : public AbcObject {
int GetObjectSize() { return sizeof(NaiveInt); }
int GetClassID() { return 1; }
char const* GetClassName() { return "NaiveInt"; }
ObjectId GetObjectId() { return static_cast<ObjectID>(this); }
}
This approach has two obvious disadvantages:
- any object derived from
AbcObject
contains an extra vtable pointer and
- all polymorphism has to be anticipated by pre-declaring the objects you inherit from.
There are also performance concerns which I will leave up to the reader to explore for themselves (hint: look at the file dispatch-timings-test.hpp).
OOTL Primitives Compared to C++ Primitives
There is one disadvantage of object oriented primitives when compared to the built-in primitives, they are slightly slower. The advantages of OOTL primitives are that they are more easily debugged and modified, they can be inherited, and delegated to, used polymorphically, and are initialized automatically. The decision to use OOTL primitives is up to the programmer of course, but I recommend giving them careful thought for your next project. You may be surprised.
Final Words
If interest in the OOTL is sufficient, I will write a follow-up article on the OOTL collection classes. Until then, have fun playing with the OOTL, and don't be shy modifying the heck out of it. Let me know if you get it to do anything interesting, or share your code and ideas with the OOTL discussion group at Google.