Преобразование нескольких наследований из базового класса в другой производный класс - PullRequest
14 голосов
/ 15 сентября 2011

Давайте предположим, что существует такая иерархия классов:

class A //base class

class B //interface

class C : public A, public B

Затем создается объект C:

A *object = new C();

Возможно ли привести объект к B?

Важно: Я предполагаю, что не знаю, что это за объект C. Я просто знаю, что он реализует интерфейс B

Ответы [ 5 ]

12 голосов
/ 15 сентября 2011

Нет . Это невозможно (прямое приведение от A* к B*).

Поскольку адреса A и B находятся в разных местах в class C. Таким образом, актерский состав всегда будет небезопасным, и, возможно, вы можете оказаться в неожиданном поведении . Демо .

Кастинг всегда должен проходить через class C. например,

A* pa = new C();
B* pb = static_cast<C*>(pa);
                   ^^^^ go through class C

Демо

8 голосов
/ 15 сентября 2011

Способ перехода из любого типа в любой другой - dynamic_cast .Но для этого требуется, чтобы объект был полиморфным .В общем случае для этого требуется, чтобы v-таблица была связана с A и B, поэтому: если A и B имеют хотя бы одну виртуальную функцию, а RTTI не отключен,

A* pa1 = new C;
A* pa2 = new A;

B* pb1 = dynamic_cast<B*>(pa1);
B* pb2 = dynamic_cast<B*>(pa2);

будетв результате pb2 будет нулевым, а pb1 будет указывать на B-часть объекта, содержащую * pa1 в качестве A-части.(Факт, что это C или что-то другое, полученное из этих двух оснований, не имеет значения).

В противном случае, когда все должно быть статичным, вы должны пройти C

B* pb = static_cast<B*>(static_cast<C*>(pa));

Примечаниечто static_cast<B*>(pA) не может компилироваться, так как A и B не связаны друг с другом.

3 голосов
/ 15 сентября 2011

Да, сначала вы должны static_cast возразить на C *, затем вы можете static_cast снова на B (хотя это последнее приведение не требуется, поскольку это стандартное преобразование).Я не уверен, будет ли работать static_cast объект непосредственно в B, попробуйте и посмотрите, если вы получаете ошибки компилятора.reinterpret_cast Передача объекта в B приведет к аварийному завершению во время выполнения, так как A и B будут иметь разные адреса, если они оба не пустые.

Edit После того, как вы изменили вопрос, онбольше не возможно делать то, что вы хотите.Вам необходимо знать правильный путь вверх и вниз по дереву наследования, поскольку приведение в сценарии с множественным наследованием с непустыми классами подразумевает сдвиги в указателе.

0 голосов
/ 22 февраля 2019

Конечно, вы можете. Логически, если вы определенно уверены, что объект 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 позволит вам получить чистый, даже без предупреждения, результат компиляции, так как приведение типизированного указателя к пустому указателю вполне разумно.

Это всего лишь одно из множества возможных решений, поэтому, если вы ищете решение, которое можно использовать в разных ситуациях, пожалуйста, дайте мне знать.

Мой английский не очень хорош, и в контексте могут быть грамматические ошибки.

0 голосов
/ 15 сентября 2011

Пока объект получен из B, вы всегда можете привести объект к B. И, конечно, вы можете вызывать только методы, определенные в интерфейсе B, потому что виртуальный указатель может получить доступ только к методу, определенному B в виртуальный стол.

...