Конечно, вы можете. Логически, если вы определенно уверены, что объект X имеет тип A, то это означает, что вы можете использовать его как A.
Простой и наивный способ добиться этого - использование dynamic_cast, предоставляемого из стандарта C ++. Однако он будет использовать линейное время для просмотра vtable, потому что dynamic_cast должен проверить, действительно ли данный указатель может быть приведен к данному типу, в отличие от вас, которые уже знают, что X является типом A. Некоторые платформы могут просто не обеспечивать RTTI. , что позволит вам сделать dynamic_cast.
Есть другое решение: пусть один из A и B знает, что сам может быть суперклассом, который выполняет множественное наследование.
#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;
}
В этом примере кода есть 3 базовых класса, 2 функции получения и несколько подклассов. Widget и DbItem - это классы, к которым вы не можете прикоснуться, а GeneralItem предназначен для использования в качестве суперкласса для классов, которые выполняют множественное наследование.
getDbItem() : GeneralItem*
- это функция, которая возвращает GeneralItem, который, безусловно, также является экземпляром DbItem. getWidget() : GeneralItem*
аналогично тому, какой тип возвращаемого экземпляра должен быть Widget.
GeneralItem имеет виртуальную функцию cast(type_id) : void*
, которая возвращает указатель данного типа, но в виде пустого указателя, потому что тип возвращаемого значения определяется во время компиляции. Его можно преобразовать обратно в нужный вам тип с помощью static_cast или reinterpret_cast: любой из них может быть использован.
Вам нужно обратить внимание на произвольный порядок суперклассов и static_cast в реализации cast()
: если вы пропустите static_cast, ваш указатель void будет отображен на дочерний класс, а не на DbItem
или Widget
. Пропуск static_cast позволит вам получить чистый, даже без предупреждения, результат компиляции, так как приведение типизированного указателя к пустому указателю вполне разумно.
Это всего лишь одно из множества возможных решений, поэтому, если вы ищете решение, которое можно использовать в разных ситуациях, пожалуйста, дайте мне знать.
Мой английский не очень хорош, и в контексте могут быть грамматические ошибки.