C++ dynamic_cast - polymorphic requirement and dow

2019-02-09 01:09发布

问题:

In the following code, while constructing obj in case 1, we construct a derived class object too, but its member functions are just inaccessible to obj. So while downcasting (i.e., in case 2), using obj as source, we have the constructed derived in it already. Why would obj need to be polymorphic?

If I confused you with my above description, why doesn't obj need to be polymorphic when upcasting, but while downcasting it does need to be polymorphic while using 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

回答1:

From 5.2.7/1 [expr.dynamic.cast] :

The result of the expression dynamic_cast<T>(v) is the result of converting the expression v to type T.

[...]

If T is "pointer to cv1 B" and v has type "pointer to cv2 D" such that B is a base class of D, the result is a pointer to the unique B sub-object of the D object pointed to by v.

[...]

Otherwise, v shall be a pointer to or an lvalue of a polymorphic type.

The standard even provides the following example which illustrates that the polymorphic type requirement does not stand for derived to base conversion :

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


回答2:

In order for dynamic_cast to work the object needs to be polymorphic. The reason for this is that dynamic_cast needs somewhere to store the type information that is will use to perform the cast, and it does this by storing the information alongside the vtable for the class. In order for there to be a vtable you need to make at least one of your methods virtual.

The easiest way around this is to flag the base class destructor as virtual.

Upcasting (ie derived to base) doesn't needs a cast as the compiler is able to check that the cast would work at compile time. However, the same isn't true when downcasting.



回答3:

Dynamic_cast

  • It is used to cast a base pointer into a derived pointer. If the base pointer doesn't point to an object of the type of the derived, it returns
  • It is used to cast a base reference into a derived reference. If the reference isn't pointing to an object of the derived, it throws std::bad_cast.
  • It can be considered the checked cast equivalent to static_cast, in that it checks whether the object pointed to really is of the derived type.

You must read more about Dynamic_cast (with example) there.



回答4:

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

In the above example most compilers would implement dynamic cast by checking whether the vtable pointer of b points to the vtable of the derived class D or not. If yes, it simply returns the address of b as the return value otherwise it returns a nullptr. This is what possibly goes on behind the scenes when a dynamic cast executes :-

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();
        }
    }
}

Now, if the class is not polymorphic, there is no way for the compiler to find whether pCar is pointing to honda or toyota car. Note that this is just one of the ways to implement dynamic_cast as the C++ standard does not talk anything about vtables.