Click here to Skip to main content
15,890,557 members
Articles / Programming Languages / C++
Article

Polymorphism without Planning (under the hood)

Rate me:
Please Sign up or sign in to vote.
4.84/5 (12 votes)
28 Nov 20042 min read 64.5K   32   15
This article explains the techniques used to allow the interface reference types to be polymorphic on any type which provides matching function signatures.

Introduction

My previous article, Polymorphism without Planning, discussed how the OOTL (Object Oriented Template Library) uses a technique implemented by the BIL (Boost Interfaces Library) which is included with the OOTL release version 0.1. This left a lot of people curious about what was going on under the hood.

Background

The BIL allows any object that implements a set of functions which match those of a declared interface, to be referred to using a single type:

class Dog {
  public:
    const char* MakeSound() { return "woof"; }
};

class Duck {
  public:
    const char* MakeSound() { return "quack"; }
};

BOOST_IDL_BEGIN(IAnimal)
  BOOST_IDL_FXN0(MakeSound, const char*)
BOOST_IDL_END(IAnimal)

int main() {
  Dog dog;
  Duck duck;
  IAnimal animal = dog;
  puts(animal.MakeSound()); // prints woof
  animal = duck;
  puts(animal.MakeSound()); // prints quack
  return 0;
};

The question that I am asked frequently, is how in fact can we have statically typed interfaces in C++, without placing any extra information in the object?

Double-Width Pointers

In order to achieve any kind of dynamic dispatch, we require a function table lookup somewhere in our code, otherwise we wouldn't be able to have run-time polymorphism. The interface reference is then represented internally as a double width pointer; one pointer points to the object, while the other object points to a function table. This function table is created statically at compile-time using templates.

A function table is created for every class-to-interface type-cast in the code. This is done using template versions of the assignment operator and initializing constructor.

Creating Interfaces Reference Types by Hand

Dave Abrahams of Boost-Consulting.com posted the following code to comp.std.c++ on 2004-04-25 which was an improvement on my original proposal, and the technique used by the interface code generating tool HeronFront.

// a baz "interface"
class baz
{
 private:
    // forward declarations
    template <class T>
    struct functions;

 public:
    // interface
    template <class T>
    baz(T& x) : _m_a(&x), _m_t(&functions<T>::table)
    {}

    int foo(int x)
    { return _m_t->foo(const_cast<void*>(_m_a), x); }

    int bar(char const* x)
    { return _m_t->bar(const_cast<void*>(_m_a), x); }

 private:
    // Function table type for the baz interface
    struct table_type
    {
        int (*foo)(void*, int x);
        int (*bar)(void*, char const*);
    };

    // For a given referenced type T, generates functions for the
    // function table and a static instance of the table.
    template <class T>
    struct functions
    {
        static baz::table_type const table;

        static int foo(void* p, int x)
        { return static_cast<T*>(p)->foo(x); }

        static int bar(void* p, char const* x)
        { return static_cast<T*>(p)->bar(x); }
    };

    void const* _m_a;
    table const* _m_t;
};

template <class T>
baz::table_type const
baz::functions<T>::table = {
    &baz::functions<T>::foo
  , &baz::functions<T>::bar
};

struct some_baz {
    int foo(int x) { return x + 1; }
    int bar(char const* s) { return std::strlen(s); }
};

struct another_baz {
    int foo(int x) { return x - 1; }
    int bar(char const* s) { return -std::strlen(s); }
};

int main()
{
    some_baz f;
    another_baz f2;
    baz p = f; 
    std::printf("p.foo(3) = %d\n", p.foo(3));
    std::printf("p.bar('hi') = %d\n", p.bar("hi"));
    p = f2;
    std::printf("p.foo(3) = %d\n", p.foo(3));
    std::printf("p.bar('hi') = %d\n", p.bar("hi"));
}

About the Code

The above code defines an interface reference named baz manually, which can refer to any type which provides functions matching the function pointers in the baz::table.

Every interface reference variable stores a pointer to its function table through the variable baz::_m_t and stores a pointer to the object in baz::_m_a.

What the code does is generate a static function table for every class T that is passed to a baz. These static function tables have type baz::table_type and are named baz::function<T>::table. Even though there is only one name, because it is a static template variable of baz::function, there is a separate one created for every instance of baz::function. To put it another way, we use the compiler to generate a function table for every class - interface pair possibility at compile-time.

Summary

The technique described, even though it is sophisticated, is still a simplification of how the BIL is implemented. The BIL is considerably more complex because it also provides support for a wide range of techniques such as Aspect Oriented Programming, Delegations, Generic Programming, and more. The BIL is also required to work around certain limitations of the C++ pre-processor. I will write more in the future about the BIL as it matures, and is released officially into the public domain. Hopefully, this article does help explain the theory behind the technique and can provide you with some insight into the interface reference types.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer Ara 3D
Canada Canada
I am the designer of the Plato programming language and I am the founder of Ara 3D. I can be reached via email at cdiggins@gmail.com

Comments and Discussions

 
GeneralHere's an entirely different implementation Pin
Don Clugston5-Dec-04 13:04
Don Clugston5-Dec-04 13:04 
GeneralRe: Here's an entirely different implementation Pin
Christopher Diggins5-Dec-04 15:09
professionalChristopher Diggins5-Dec-04 15:09 
GeneralRe: Here's an entirely different implementation Pin
Don Clugston5-Dec-04 17:35
Don Clugston5-Dec-04 17:35 
GeneralFascinating stuff Pin
Don Clugston1-Dec-04 19:58
Don Clugston1-Dec-04 19:58 
GeneralRe: Fascinating stuff Pin
Christopher Diggins2-Dec-04 7:15
professionalChristopher Diggins2-Dec-04 7:15 
GeneralRe: Fascinating stuff Pin
Hofver7-Dec-04 22:41
Hofver7-Dec-04 22:41 
GeneralRe: Fascinating stuff Pin
Don Clugston8-Dec-04 17:11
Don Clugston8-Dec-04 17:11 
GeneralRe: Fascinating stuff Pin
Hofver9-Dec-04 7:48
Hofver9-Dec-04 7:48 
I think it's a 'yes'! At least it has encouraged me to give it a try.

I wasn't thinking about removing state-based logic, though. The thing that's been annoying me about COM implementation is that you can't make a base class that handles the IUnknown trivia (reference counting and interface queries) and then just inherit that in every COM object you make. Similarly, you can't make a class that implements the IOneInterface, another class that implements the IOtherInterface, and then simply make a COM object out of the two. Well perhaps you can, but then you have to put the class in a header file, or stomp it into a macro or define it in a template or however the deuce the ATL is working. It's far too complicated to be useful, in my opinion.

What could perhaps be used instead of ATL etc is the following:

* Make two separate hierarchies, one for interfaces (always abstract virtual classes inheriting one abstract virtual class).

* The other hierarchy contains the implementations of interfaces. One class for the implementation of IUnknown (at least AddRef and Release), one class for the implementation of IWhateverInterface, etc, etc.

* Some of the implementation classes might inherit from each other, others not. No one needs to inherit the IUnknown implementor.

* Each time you need to assemble a COM class, you create a class that inherits all the interfaces that your class should implement.

* The COM object class contains one member of each of the implementor classes for the interfaces it will support.

* The COM object class also contains either one BOOST_IDL interface for each COM interface to implement or one FastDelegate for each function it will implement.

* In the constructor of the COM class, all the BOOST_IDL interfaces or FastDelegates are initialized to member functions of the member objects of the implementor classes.

* For each of the interfaces to be supported, you implement the virtual functions of the interface by simply calling the correct delegate/BOOST interface member. This can be macro-ized for each interface separately (same code for all COM objects implementing a particular interface).

* The only thing that needs some additional tricks is the QueryInterface function, but that can also be solved with fairly simple macros (don't have to rewrite it for each object).

* Additionally, if any of the implementor classes need access to the QueryInterface function of the object it implements, just add an IUnknown pointer to the implementor class and pass it to the implementor class in the constructor of the COM object.

All this adds a LOT of overhead, of course. An extra eight bytes per function, extra function call overhead when calling functions etc. To me, that's not too much of a penalty to pay for more readable and understandable code (eg compared to the ATL).

Bottom line is that you can implement classes that handle specific interfaces and then inherit those classes to form a COM object, something which is not possible using standard inheritance.
Put please do shoot me if I'm wrong...

/Simon


This is not a signature.
GeneralRe: Fascinating stuff Pin
Christopher Diggins9-Dec-04 8:05
professionalChristopher Diggins9-Dec-04 8:05 
GeneralRe: Fascinating stuff Pin
Hofver9-Dec-04 8:09
Hofver9-Dec-04 8:09 
GeneralRe: Fascinating stuff Pin
Christopher Diggins9-Dec-04 8:27
professionalChristopher Diggins9-Dec-04 8:27 
GeneralRe: Fascinating stuff Pin
Hofver9-Dec-04 21:39
Hofver9-Dec-04 21:39 
General(COM and interfaces) Re: Fascinating stuff Pin
Don Clugston22-Dec-04 15:17
Don Clugston22-Dec-04 15:17 
GeneralRe: Fascinating stuff Pin
Christopher Diggins8-Dec-04 20:10
professionalChristopher Diggins8-Dec-04 20:10 
GeneralSmall code error. Pin
Christopher Diggins29-Nov-04 6:32
professionalChristopher Diggins29-Nov-04 6:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.