Re: Multiple Inheritance vs. Interface
On 26 sep, 23:39, Stuart <DerTop...@web.de> wrote:
On 9/23/12 "lieve again" wrote:
[snip]
I thought maybe making the interfaces pure virtual,
there was a way to avoid the extra vpointers and I wanted to know how.
Then if I start adding pure virtual classes to impose the derived
classes with some kind of features like:
class Derived : implements Readable, Writeable, Comparable,
Convertible ...
regardless of the programming language, we are ending with instances
of the derived classes having 20 bytes or more even being those
classes with no members or empty. It is good to know.
There is one way to avoid object bloat, but you are not going to like it =
:-)
The following code uses a hand-made vtable substitute. For that reason I
had to introduce a special pointer type which stores the class's ID
together with the pointer to the object. This class ID is assigned by
hand, so that this scheme will only work for class hierarchies that will
not get extended (if you want to use this for extendable class
hierarchies, you'd have to use growable look-up table instead of a fixed
array of method pointers, but then you had better use objective-C++).
Note that the base class Base in my example must not contain any virtual
methods, or else the class Derived will get bloated again. Of course,
this makes the code really awfull to look at. However, most of it may
get generated by a some clever pre-processor magic.
Since the full code may cause some shock, I'll give a short summary:
class Base {
public:
void foo () {std::cout << "Base::foo\n";}
BasePtr operator& ();
};
class Derived : public Base {
public:
void foo () {std::cout << "Derived::foo\n";}
DerivedPtr operator&();
}
Base1Ptr, Base2Ptr and DerivedPtr and those special pointer types that
allow us to do the following:
void invokeFooVirtually (const BasePtr& b) {
b.foo();
}
int main () {
Base b;
Derived d;
invokeFooVirtually(&b);
invokeFooVirtually(&d);
}
will print:
Base1::foo
Derived::foo
There is a price you have to pay: The pointer types are now twice as
large (the xxxPtr contains not only the raw pointer but also the class's
ID). Furthermore the cast from DerivedPtr to BasePtr has to call the
cast operator of DerivedPtr.
A compilable example with two base classes and some members:
#include <iostream>
class Base1Ptr {
protected:
static const int classID;
class Base1* ptr;
int objectID;
public:
Base1Ptr (class Base1* ptr, int objectID = classID)
: ptr(ptr), objectID(objectID) {}
void foo () const;};
const int Base1Ptr::classID = 0;
class Base1 {
protected:
int base1Int;
public:
Base1 (int i) : base1Int(i / 2) {}
void foo () { std::cout << "Base1::foo with base1Int = "
<< base1Int <<=
"\n";}
Base1Ptr operator& () {return Base1Ptr(this);}
};
class Base2Ptr {
protected:
static const int classID;
class Base2* ptr;
int objectID;
public:
Base2Ptr (class Base2* ptr, int objectID = classID)
: ptr(ptr), objectID(objectID) {}
void bar () const;};
const int Base2Ptr::classID = 0;
class Base2 {
protected:
int base2Int;
public:
Base2(int i) : base2Int(i * 2){}
void bar () { std::cout << "Base2::bar with base2Int = "
<< base2Int <<=
"\n";}
Base2Ptr operator* ();
};
class DerivedPtr {
class Derived* ptr;
int objectID;
public:
DerivedPtr (class Derived* ptr, int objectID)
: ptr(ptr), objectID(objectID) {}
operator Base1Ptr();
operator Base2Ptr();
};
class Derived : public Base1, public Base2 {
static const int classID;
public:
Derived (int i) : Base1(i), Base2(i){}
void foo () { std::cout << "Derived::foo with base1Int = "
<< base1Int <<=
"and base2Int = "
<< base2Int <<=
"\n";}
void bar () { std::cout << "Derived::bar\n";}
DerivedPtr operator&() {return DerivedPtr(this,classID);}
};
const int Derived::classID = 1; // std::max(Base1::classID,
// =
Base2::classID) + 1;
DerivedPtr::operator Base1Ptr() {return Base1Ptr(ptr, objectID);}
DerivedPtr::operator Base2Ptr() {return Base2Ptr(ptr, objectID);}
// Base1Ptr uses the following table to look up
// the correct member function.
typedef void (Base1::*FooPtr)(void);
FooPtr fooVTable[] = {&Base1::foo, (FooPtr)&Derived::foo};
void Base1Ptr::foo () const
{
FooPtr targetFooFunction = fooVTable[objectID];
(ptr->*targetFooFunction)();
}
// The same goes for Base2.
typedef void (Base2::*BarPtr)(void);
BarPtr barVTable[] = {&Base2::bar, (BarPtr)&Derived::bar};
void Base2Ptr::bar () const
{
BarPtr targetFooFunction = barVTable[objectID];
(ptr->*targetFooFunction)();
}
// Note that the cast of the addresses of members of Derived
// to addresses of members of Base results in UB.
// However, the results are pretty much as expected.
void invokeFoo (Base1* b) {
b->foo();
}
void invokeFooVirtually (const Base1Ptr& b) {
b.foo();
}
void invokeBarVirtually (const Base2Ptr& b) {
b.bar();
}
int main () {
Base1 b(3);
Derived d(42);
std::cout << "sizeof(Base1) == " << sizeof(Base1) << "\n";
std::cout << "sizeof(Derived) == " << sizeof(Derived) << "=
\n";
std::cout << "sizeof(Base1*) == " << sizeof(Base1*) << "\n=
";
std::cout << "sizeof(Base1Ptr) == " << sizeof(Base1Ptr) <<=
"\n";
std::cout << "sizeof(Derived*) == " << sizeof(Derived*) <<=
"\n";
std::cout << "sizeof(DerivedPtr) == " << sizeof(DerivedPtr=
) << "\n";
invokeFooVirtually(&b);
invokeFooVirtually(&d);
invokeBarVirtually(&d);
}
Regards,
Stuart
Interesting (and complex) example. I think, I have understood it, its
a way to avoid the extra vpointers in class by make the base pointers
"fatter". Probably some programming language have implemented multiple
inheritance that way.
Looking the code, another way to do it were:
// typedef void (Base1::*FooPtr)(void);
// FooPtr fooVTable[] = {&Base1::foo, (FooPtr)&Derived::foo};
void Base1Ptr::foo () const
{
FooPtr targetFooFunction = fooVTable[objectID];
(ptr->*targetFooFunction)();
if(this->objectID != 0){
Derived* derived = static_cast<Derived*>(ptr);
derived->foo();
}else // not derived, call it normally
ptr->foo();
}
With the objectID we already know the true class of the base pointer,
so we could simply convert it safely to the derived class, in that way
we can avoid the pointer to member functions which they are a little
bit complicated. Instead we can made a table of int like an index to
know the true class or at least if we need to use virtual functions,
something like:
enum Index { BASE1 = 0, BASE2 = 1, BASE3 = 2, DERIVED = 3};
an depending of the table answer, convert it to the right class.
We could store the object id directly in the Base1, and forget about
the BasePtr and the conversion operators, but this would made the base
classes bigger, so the Base1 approach its better: we make the pointer
fatter only if an conversion from the derived class to a base class
take place.
Thanks!!