|
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());
animal = duck;
puts(animal.MakeSound());
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.
class baz
{
private:
template <class T>
struct functions;
public:
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:
struct table_type
{
int (*foo)(void*, int x);
int (*bar)(void*, char const*);
};
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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 15 of 15 (Total in Forum: 15) (Refresh) | FirstPrevNext |
|
 |
|
|
My FastDelegate class can also be used to retrofit polymorphism. (I've just made a very nice hammer, and of course that means I tend to see every problem as a nail ). http://www.codeproject.com/cpp/FastDelegate.asp
With your 'Dog' and 'Duck' classes:
struct IAnimal { const FastDelegate1<const char *> MakeSound; template <class X> IAnimal(X *pX) : MakeSound(pX, &X::MakeSound) {} };
IAnimal animal = dog; puts(animal.MakeSound()); // prints woof
IAnimal animal = duck; puts(animal.MakeSound()); // prints quack
To include more functions in the interface, just add more FastDelegate members, and initialize them in the constructor.
// the return value is the last parameter struct baz { const FastDelegate1<int, int> foo; const FastDelegate1<char * const, int> bar; template <class X> baz(X* pX) : foo(pX, &X::foo), bar(pX, &X::bar) {} };
Advantages compared to your method: * No macros required - looks like ordinary C++. * Invoking one of these functions is optimally efficient (doesn't require a second indirection). * By overloading the interface constructor, you can support classes where the function names aren't the same. Eg, File::Load and Archive::Restore might have the same signature and the same functionality, so you might want to use the interface. In fact, the interface could even be split between multiple classes. * When there is only a single function, my method is optimal in every respect.
There's one BIG disadvantage compared to your method: * The IObject isn't lightweight, except in the degenerate case where the interface has only a single function.
The size is 2*sizeof(void *)* number_of_functions. Setting each function is usually only two machine instructions, but still, it probably wouldn't be suitable if you were rapidly switching large interfaces between different classes.
If you were making a serious proposal to C++0x, it might be worth discussing this kind of implementation. (And almost certainly discarding it, because it wouldn't let you use 'interface' to check template parameters, which would be a fantastic feature).
However, the implementation is so simple that I think I will make a mention of it my article. And you might find it interesting to play around with.
-Don.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Fascinating stuff! Your article it is long though, so it is going to take me some time to completely digest it. If you don't mind, I have a few questions for you. Is the following possible?
IAnimal animal = dog; puts(animal.MakeSound()); // prints woof animal = duck; // notice the same animal variable is reused, not redeclared puts(animal.MakeSound()); // prints quack
Also does the following work as expected:
IAnimal animal = some_abstract_animal; // where MakeSound is virtual puts(animal.MakeSound()); // prints appropriate sound
Thank you very much for sharing this with me Don, your work is very impressive.
Christopher Diggins www.CDiggins.com www.Heron-Language.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
> Is the following possible? animal = duck; // notice the same animal variable is reused, not redeclared
Probably not, in the code I posted. I made the members const so that they could only be set in the constructor, to mimic your code. If they weren't declared as const, you'd be able to say animal.MakeSound.bind(dog, &Dog::MakeSound); or animal.MakeSound = &SomeStaticFunction; and so course an assignment operator could be made. Maybe you could just keep strip the consts away and write animal = IAnimal(duck); to keep the interface declaration simple? Try it and see.
> Also does the following work as expected:
IAnimal animal = some_abstract_animal; // where MakeSound is virtual puts(animal.MakeSound()); // prints appropriate sound
Yes. And it works on most (all?) platforms with template support equivalent to VC6 or better.
> Thank you very much for sharing this with me Don, your work is very impressive.
Thanks! I've been overwhelmed by the response it's been getting. Even Scott Meyers and some members of the standards comittee have read it. Maybe I should get a job as a programmer .
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I think what we have here is a template version of virtual functions. In fact you could use a similar technique to do virtual functions 'by hand' as if inheritance was not part of the language. It's a shame that C++ isn't quite powerful enough to give you decent syntax. I hope that C++0x does get template metaprogramming powerful enough to implement this sort of thing cleanly. They really should fix up the member function pointer debacle too. MFPs are the only runtime polymorphism alternative to inheritance, so they should assume much greater importance in a generic programming world.
One thing I haven't seen mentioned in your articles is Microsoft's __declspec(novtable)<\CODE> compiler extension (also available with the __interface keyword). Although it doesn't allow "polymorphism without planning", it does provide most of the performance benefits your library seeks.
Anyway, you've just convinced me that generic programming is far more powerful than OOP, rather than just being an alternative paradigm. I've gradually been coming to the conclusion that OOP is fundamentally flawed: I don't think IS-A relationships *ever* exist in the real world. Instead, what we have is CAN-BE-TREATED-AS-A relationships. And an object shouldn't have to predict how it will be treated. Your interfaces express this concept much better -- but I wonder what programs written this way would actually look like. I suspect there is a lot of work to turn this concept into a full programming paradigm.
Great work!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank you very much for the encouragement.
I view this as the beginning of a separate paradigm which I call interface oriented programming. Interfaces are perfect for modeling CAN-BE-TREATED-AS-A relationships which I agree occur far more frequently when modeling relationships between objects. Even though interfaces can be implemented through generic programming, it will always be somewhat awkward unless given proper syntax in the language. I have made a proposal at http://www.heron-language.com/cpp-iop.html[^] to add an interface keyword into the C++ language.
My programming language Heron ( http://www.heron-langauge.com[^] ) is built upon the interface oriented approach though the compiler does not yet support interfaces.
For extensive examples of interface oriented programming you can check out the Object Oriented Template Library at http://www.ootl.org which uses interfaces extensively.
Christopher Diggins www.CDiggins.com www.Heron-Language.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks both cdiggins and Don Clugston for interesting articles and an interesting discussion. I agree that OOP is not the ultimate hammer that will turn everything into nails. For some time now I've found myself thinking not so much about what kinds my objects are and what kinds they are not and other subtleties of OOP but more about how to get code executed at the right time as easily as possible, with easy access to the data it needs. Preferrably without having to plan ahead.
Here's a questions for you both: Could your articles be used when creating COM objects, to facilitate implementation inheritance? I'm not an expert in either of these fields and perhaps you're not into COM at all, but if you are it would be really interesting to hear what you have to say about this.
This is not a signature.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The COM issue is an interesting one. Because on a binary level, COM interfaces are MSVC vtables, it's not possible to replace a COM interface *directly* with either of our schemes.
I suspect that you could achieve a lot with either of our schemes, but it would take some thought. You could certainly use my article to simplify COM aggregation, and it might be helpful for tear-off interfaces. I think it's most likely, though, to be useful in eliminating state-based logic (which in my experience COM objects are full of), rather than interfaces per se.
I think cdiggin's method has more chance at implementing a full COM interface, because he's already doing similar clever tricks with macros, and because his interfaces seem a better match for COM (mine are more dynamic than is really required).
Hmmm. I don't know if I've given you a 'yes' or 'no' answer!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
You're right, 8 bytes per function is only if using FastDelegates, using OOTL/BIL it will only be four bytes per function plus four bytes per interface, right?
This is not a signature.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hm, replying to myself here. A couple of hours after posting this thing, it suddenly hit me that there's no need at all for either BILs or FastDelegates. If I'm instantiating each of the virtual functions in the interfaces to implement, I might as well call the implementation functions directly... which is slightly faster and definitely less memory-consuming (even if BILs only take 8 bytes per interface).
So OK, there's no need using these interfaces/delegates in that scheme, but is there a way, probably using BILs, to not have to instantiate these virtual function in the COM object at all??
/Simon
This is not a signature.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Simon, I'd be very interested to hear any results you get from this. I don't have enough experience with COM to be able to guess how well this would work.
> 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.
> 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).
I think there is a reasonable chance that a simplified code structure could give benefits that would offset those overheads. For example, it's easy to imagine the code size being much smaller.
I don't think anyone has tried to do anything much like this before, so your experience would be very interesting to hear about.
Don.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|