C++ dynamic_cast — полиморфное требование и понижение

В следующем коде при создании obj в случае 1 мы также создаем объект класса derived, но его функции-члены просто недоступны для obj. Таким образом, при понижении (т.е. в случае 2), используя obj в качестве источника, мы уже имеем в нем сконструированное derived. Почему obj должен быть полиморфным?

Если я запутал вас своим приведенным выше описанием, почему obj не должен быть полиморфным при восходящем приведении, но при понижении он должен быть полиморфным при использовании dynamic_cast?

class base
{
public:
    base()
    {
        cout<< " \n base constructor \n";
    }
};

class derived : public base
{
public:
    derived()
    {
        cout << " \n derived constructor \n";
    }
};

base *obj = dynamic_cast<base*> (new derived); // case 1: explicitly upcasting
derived *OBJ = dynamic_cast<derived*> (obj);   // case 2: error

person Mahesh    schedule 10.01.2011    source источник


Ответы (4)


Из 5.2.7/1 [expr.dynamic.cast]:

Результатом выражения dynamic_cast<T>(v) является результат преобразования выражения v в тип T.

[...]

Если T является «указателем на cv1 B», а v имеет тип «указатель на cv2 D», так что B является базовым классом D, результатом является указатель на уникальный подобъект B объекта D, на который указывает v.

[...]

В противном случае v должен быть указателем или lvalue полиморфного типа.

Стандарт даже предоставляет следующий пример, который иллюстрирует, что требование полиморфного типа не означает преобразование производного в базовый:

struct B {};
struct D : B {};
void foo(D* dp)
{
    B* bp = dynamic_cast<B*>(dp); // equivalent to B* bp = dp;
}
person icecrime    schedule 10.01.2011

Чтобы dynamic_cast работал, объект должен быть полиморфным. Причина этого в том, что dynamic_cast нужно где-то хранить информацию о типе, которая будет использоваться для выполнения приведения, и он делает это, сохраняя информацию вместе с виртуальной таблицей для класса. Чтобы была виртуальная таблица, вам нужно сделать хотя бы один из ваших методов виртуальным.

Самый простой способ обойти это — пометить деструктор базового класса как виртуальный.

Повышение приведения (т.е. преобразование в базовое) не требует приведения, поскольку компилятор может проверить, будет ли приведение работать во время компиляции. Однако то же самое не верно при понижении.

person Sean    schedule 10.01.2011
comment
в этом примере вы могли бы использовать static_cast. Во время компиляции вы знаете, что obj содержит правильный класс. Использование dynamic_cast - это когда вы не знаете во время компиляции, является ли объект производным от другого. Например, когда вы получаете указатель из функции и вам нужно проверить, принадлежит ли возвращенный объект к определенному классу. (Но вы правы, они должны быть виртуальными.) - person RedX; 10.01.2011

Dynamic_cast

  • Он используется для приведения базового указателя к производному указателю. Если базовый указатель не указывает на объект производного типа, он возвращает
  • Он используется для приведения базовой ссылки к производной ссылке. Если ссылка не указывает на производный объект, выдается std::bad_cast.
  • Его можно считать проверенным приведением, эквивалентным static_cast, поскольку оно проверяет, действительно ли объект, на который указывает указатель, относится к производному типу.

Вы должны прочитать больше о Dynamic_cast (с примером) здесь.

person G-71    schedule 10.01.2011

B* b = new D();
D* d = dynamic_cast<D*>(b);

В приведенном выше примере большинство компиляторов реализуют динамическое приведение, проверяя, указывает ли указатель vtable для b на vtable производного класса D или нет. Если да, он просто возвращает адрес b в качестве возвращаемого значения, в противном случае он возвращает nullptr. Это то, что, возможно, происходит за кулисами, когда выполняется динамическое приведение: -

class car
{
    public:
    virtual void drive()
    {
         std::cout <<"car"<<std::endl;
    }
};
class toyota: public car
{
    public:
    virtual void drive()
    {
        std::cout <<"toyota"<<std::endl;
    }
};

class honda: public car
{
    public:
        virtual void drive()
    {
        std::cout <<"honda"<<std::endl;
    }
};

template <typename Tderived>
Tderived* dynamicCast(void* pBase)
{
    //compare the vptr of the class pointed by pBase with a temporary Tderived class. 
    //If vptr of pBase and vptr of Tderived() are pointing to the same vtable 
    //then it can be safely deduced that pBase is indeed pointing to an instance of Tderived
    if (*(int**)pBase == *(int**)&Tderived())
    {
        return (Tderived*)pBase;
    }
    else
    {
        return nullptr;
    }
}


int main()
{
    car* pCar;
    honda hondaCar;
    toyota toyotaCar;

    pCar = &toyotaCar;

    honda* pHonda = dynamicCast<honda>(pCar);
    if (nullptr != pHonda)
    {
        pHonda->drive();
    }
    else
    {
        toyota* pToyota = dynamicCast<toyota>(pCar);
        if (nullptr != pToyota)
        {
            pToyota->drive();
        }
    }
}

Теперь, если класс не является полиморфным, компилятор не сможет определить, указывает ли pCar на автомобиль Honda или Toyota. Обратите внимание, что это всего лишь один из способов реализации dynamic_cast, поскольку стандарт C++ ничего не говорит о виртуальных таблицах.

person Vishal    schedule 04.03.2016