Search code examples
c++polymorphismundefined-behaviordynamic-cast

Make sure that dynamic_cast won't cause undefined behaviour C++


Up to this day I thought that if I have class A and B that are not related in any way, the dynamic_cast using pointers would fail returning nullptr. Now I've read that this causes undefined behaviour...

cppreference - dynamic_cast:

When dynamic_cast is used in a constructor or a destructor (directly or indirectly), and expression refers to the object that's currently under construction/destruction, the object is considered to be the most derived object. If new_type is not a pointer or reference to the constructor's/destructor's own class or one of its bases, the behavior is undefined.

I have code like this:

class ActorComponent
{
    ActorComponent* m_parent;
public:
    template <typename _type>
    _type* getParent() const {
        return dynamic_cast<_type*>(m_parent);
    }

    ActorComponent(ActorComponent* parent) {
        // using `getParent` inside the constructor:
        auto p = this->getParent<int>(); // `int` obviously isn't base of the ActorComponent
    }
};

I need to limit _type to type that is either

  • equal to ActorComponent
  • derived from ActorComponent

... and possibly more.

The question is: how to make sure in compile-time that dynamic_cast to such _type won't cause undefined behaviour? Will simple

template <typename _type,
    typename = std::enable_if_t< std::is_base_of_v<ActorComponent, std::remove_cv_t<_type> > || std::is_same_v<ActorComponent, std::remove_cv_t<_type> > >

always work?


Solution

  • tl;dr It's fine. For some reason you picked up on a condition that clearly doesn't apply to your code.


    Your quote:

    If new_type is not a pointer or reference to the constructor's/destructor's own class or one of its bases, the behavior is undefined.

    is obviously about using dynamic_cast inside a constructor or destructor, although for some reason you omitted the complete context:

    Only the following conversions can be done with dynamic_cast, ...

    1) ... 5) ...

    6) When dynamic_cast is used in a constructor or a destructor (directly or indirectly), and expression refers to the object that's currently under construction/destruction, the object is considered to be the most derived object. If new_type is not a pointer or reference to the constructor's/destructor's own class or one of its bases, the behavior is undefined.

    Since you're not using it inside a constructor or destructor, case 6 is obviously irrelevant.

    The case actually relevant to your code is:

    5) If expression is a pointer or reference to a polymorphic type Base, and new_type is a pointer or reference to the type Derived a run-time check is performed:

    a) The most derived object pointed/identified by expression is examined. If, in that object, expression points/refers to a public base of Derived, and if only one subobject of Derived type is derived from the subobject pointed/identified by expression, then the result of the cast points/refers to that Derived subobject. (This is known as a "downcast".)

    b) Otherwise, if expression points/refers to a public base of the most derived object, and, simultaneously, the most derived object has an unambiguous public base class of type Derived, the result of the cast points/refers to that Derived (This is known as a "sidecast".) c) Otherwise, the runtime check fails. If the dynamic_cast is used on pointers, the null pointer value of type new_type is returned. If it was used on references, the exception std::bad_cast is thrown.


    Examining the code edited in later:

    ActorComponent(ActorComponent* parent) {
        // using `getParent` inside the constructor:
        // `int` obviously isn't base of the ActorComponent
        auto p = this->getParent<int>();
    }
    

    the first part of clause 6's condition

    When dynamic_cast is used in a constructor or a destructor (directly or indirectly) ...

    is met (the getParent code is indirectly in a constructor), but the second part:

    ... and expression refers to the object that's currently under construction/destruction ...

    is not satisfied, so clause 6 still doesn't apply.

    You're casting m_parent, which is a member subobject of the ActorComponent object you're constructing. Since member subobjects are completely constructed by the time you enter the body of your constructor, that object is not being constructed and we are not in that object's constructor. Apart from anything else, that object (m_parent) is a pointer, which doesn't have a constructor. As an aside, it's uninitialized, so your code is anyway completely illegal, but not for the reason you asked about.