Multiple inheritance casting from base class to different derived class
Let's assume there is such class hierarchy:
class A //base class
class B //interface
class C : public A, public B
Then C object is created:
A *object = new C();
Is it possibl开发者_如何学运维e to cast object to B ?
Important: I assume I don't know that object is C. I just know that it implements interface B
No. This is not possible (direct casting from A*
to B*
).
Because the address of A
and B
are at different locations in class C
. So the cast will be always unsafe and possibly you might land up in unexpected behavior. Demo.
The casting should always go through class C
. e.g.
A* pa = new C();
B* pb = static_cast<C*>(pa);
^^^^ go through class C
Demo
The way to go from any type to any other is dynamic_cast.
But it requires the object to be polymorphic.
In general this requires a v-table to be associated to both A
and B
, so:
if A and B have at least one virtual function, and RTTI is not disable,
A* pa1 = new C;
A* pa2 = new A;
B* pb1 = dynamic_cast<B*>(pa1);
B* pb2 = dynamic_cast<B*>(pa2);
will result in pb2 to be null, and pb1 to point to the B part of the object containing *pa1 as its A part. (The fact it's C or whatever other derived from those two bases doesn't matter).
Otherwise, where all needs to be static, you have to go through C
B* pb = static_cast<B*>(static_cast<C*>(pa));
Note that static_cast<B*>(pA)
cannot compile, being A and B each other unrelated.
Yes, you should first static_cast
object to C*, then you can static_cast
it again to B (although this last cast is not needed, since is a standard conversion). I'm not sure if static_cast
ing object directly to B would work, try and see if you get compiler errors. reinterpret_cast
ing object to B would get you a runtime crash, since A and B would have different addresses if they are both non-empty.
Edit After you changed the question, it is no longer possible to do what you want. You need to know the correct path up and down the inheritance tree, as casting in a scenario with multiple inheritance with non-empty classes implies shifts in the pointer.
As long as the object is derived from B, you can always cast the object to B. And of course, you can only call the methods defined in interface B, because the virtual pointer can only access the method defined by B in virtual table.
Of course, you can. Logically, if you are sure that an object X is type of A, then it means you can use it as A.
The simple and naive way to achieve this is using dynamic_cast, provided from standard C++. However it will use linear time to look into the vtable because dynamic_cast needs to check whether the given pointer actually can be casted to the given type, not like you who already know that X is type of A. Some platforms may not provide RTTI, in which case you can't use dynamic_cast.
There's another solution: let one of A or B know that it may be a superclass of that which is doing multiple inheritance.
#include <iostream>
#include <string>
struct Widget
{
virtual ~Widget() = default;
double widgetData;
};
struct DbItem
{
virtual ~DbItem() = default;
std::string nodeData;
};
struct GeneralItem
{
virtual ~GeneralItem() = default;
virtual void* cast(int type) = 0;
// virtual void const* cast(int type) const = 0; // Use this as well
// This is alternative for someone who don't like
// dynamic function!
void* ptrOfWidgetOrDbNode;
// If GeneralItem can know what it can be casted to.
// virtual Widget* toWidget() = 0;
// virtual DbItem* toDbItem() = 0;
};
enum { Widget_id, DbItem_id };
// Can be used as syntax candy.
Widget* toWidget(GeneralItem* gItem)
{
return static_cast<Widget*>(gItem->cast(Widget_id));
}
DbItem* toDbItem(GeneralItem* gItem)
{
return static_cast<DbItem*>(gItem->cast(DbItem_id));
}
struct TextView : Widget, GeneralItem
{
TextView() { widgetData = 20.0; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case Widget_id: return static_cast<Widget*>(this);
default: return nullptr;
}
}
};
struct ImageView : GeneralItem, Widget
{
ImageView() { widgetData = 40.0; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case Widget_id: return static_cast<Widget*>(this);
default: return nullptr;
}
}
};
struct SoundData : DbItem, GeneralItem
{
SoundData() { nodeData = "Sound"; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case DbItem_id: return static_cast<DbItem*>(this);
default: return nullptr;
}
}
};
struct VideoData : GeneralItem, DbItem
{
VideoData() { nodeData = "Video"; }
void* cast(int type) override
{
switch ( type )
{
// WARNING: static_cast IS MANDATORY.
case DbItem_id: return static_cast<DbItem*>(this);
default: return nullptr;
}
}
};
GeneralItem* getDbItem();
GeneralItem* getWidget();
int main()
{
{
// This is definitely subclass of Widget, but
// GeneralItem has no relationship with Widget!
GeneralItem* gItem = getWidget();
Widget* nowWidget = static_cast<Widget*>(gItem->cast(Widget_id));
std::cout << nowWidget->widgetData << std::endl;
delete gItem;
}
{
// This is definitely DbItem!
GeneralItem* gItem = getDbItem();
// DbItem* nowDbItem = static_cast<DbItem*>(gItem->cast(DbItem_id));
// You can use sugar!
DbItem* nowDbItem = toDbItem(gItem);
std::cout << nowDbItem->nodeData << std::endl;
delete gItem;
}
}
GeneralItem* getDbItem()
{
return new VideoData;
}
GeneralItem* getWidget()
{
return new TextView;
}
In this example code, there are 3 base classes, 2 getter functions, and some subclasses. Widget and DbItem are classes which you cannot touch, and GeneralItem is intended to be a superclass for classes which do multiple inheritance.
getDbItem() : GeneralItem*
is a function which returns GeneralItem which is definitely instance of DbItem as well. getWidget() : GeneralItem*
is similar one, which type of returned instance must be Widget.
GeneralItem has a virtual function cast(type_id) : void*
which returns pointer of given type, but in void pointer form because return type is determined at compile time. It can be cast back to the type you want by static_cast or reinterpret_cast.
You need to pay attention to arbitrary order of superclasses and static_cast in cast()
implementation: if you miss static_cast, your void pointer will be mapped to child class, not DbItem
or Widget
. Omitting static_cast will allow you to have clean, even without any warning compile result, since casting a typed pointer to void pointer is completely reasonable.
This is just one solution among various choices, so if you are looking for a solution which can be used in different situations, please let me know.
精彩评论