简体   繁体   中英

C++ safe alternative to dynamic_cast

In what scenarios can be reinterpret_cast used to cast from a base pointer that's actually a derived instance pointer? (via polymorphism).

Static casts do not work if the inheritance is polymorphic.

I considered this trivial scenario:

class A
{
public:
    virtual void Hello()
    {
        cout<<" A ";
    }
    virtual int GetType() { return 1; }
};

class B: public A
{
public:
    void Hello()
    {
        cout<< " B ";
    }
    void Do()
    {
        cout << " Another method of B";
    }
    int GetType() { return 2;}
};

/// ... sample/test code
A* a1 = new A();
A* b1 = new B();
A* a2;
B* b2;

if (a1->GetType() == 1)
{
    a2 = a1;
    a2->Hello();
}
else
if (a1->GetType() == 2)
{
    b2 = reinterpret_cast<B*>(a1);
    b2->Do();
    b2->Hello();
}

Mind the very naive "pseudo type identification method GetType() ) I used to decide whether I can convert them or not. Is it downright wrong to use reinterpret_casts at all, for such purposes, of avoid dynamic_casts? (ie is it a paranoic design, inherently dangerous and less flexible that can introduce unwanted trouble? Might it be safer and worth the minor performance cost to perform normal dynamic casts? I know that multiple inheritance and/or virtual inheritance will mess up any other cast operation, except for the polymorphic/dynamic one).

You can't use reinterpret_cast to downcast safely. But you can use

  • static_cast , when you know that the dynamic type of the object is (possibly derived from) the one you cast down to, and

  • dynamic_cast , of reference or pointer, if the statically known class is polymorphic.

In the other direction, for an upcast you can (but should not) use a C style cast in order to cast to an inaccessible base. It's specially supported in the standard. I have never found occasion to use it, though.

To just answer your first sentence: never.

The only valid way to convert a base pointer to a more-derived pointer statically is with a static_cast , and that only works if the base is non-virtual:

Base * b = &derived;                       // implicit Derived * => Base *

Derived * p = static_cast<Derived *>(b);   // OK, I know what *b really is

A static cast should be thought of as the opposite of an implicit conversion.

A reinterpret_cast is just outright wrong. (The only reinterpret-casts that are generally acceptable are to char pointers for the purpose of I/O.)

(When you have a pointer to a virtual base, you have no choice but to use a dynamic_cast , but that is of course because the base subobject is only determined at runtime in that case.)

you should avoid doing reinterpret_cast when possible, the main reason to use reinterpret_cast is when you are dealing with legacy code written in C otherwise static_cast and dynamic_cast should be prefered, if your design requires you to use reinterpret_cast you may want to take this as a hint that your design may not be optimal.

static_cast is ok to use for polymorphic types as long as you are sure they will always succeed otherwise you should use dynamic_cast.

Why avoid a dynamic_cast in this case? By having to call a virtual member function before doing the cast, you're likely paying more (in terms of performance) than you would by using dynamic_cast .

Calling a B method function on something that isn't a B is dangerous to do. The question is titled "safe" alternative, and your code is not safe.

Most of the time, using dynamic_cast is bad and a sign of poor design.

There are occasions when it is useful, particularly when working with versioned plugins. You load a plugin and get an object which may or may not support a new feature, and you can dynamically cast the interface (base class) you know it does support to the later version (which has derived from it). If it works, you can use the new feature, if it doesn't you have to disable this functionality or use an older way of doing things.

Sometimes you can use double-dispatch to do this kind of thing.

The only "safe" alternative to dynamic_cast I know is using the Visitor Pattern . Example ( Compiler Explorer ):

class A;
class B;

class TypeVisitor {
 public:
    virtual void VisitA(A&) = 0;
    virtual void VisitB(B&) = 0;
    virtual ~TypeVisitor() = default;
};

class A
{
public:
    virtual void Hello()
    {
        cout<<" A\n";
    }

    virtual int GetType() { return 1; }

    virtual void Accept(TypeVisitor& visitor) {
        visitor.VisitA(*this);
    }
};

class B: public A
{
public:
    void Hello() override
    {
        cout<< " B\n";
    }

    void Do()
    {
        cout << " Another method of B\n";
    }

    int GetType() override { return 2; }

    void Accept(TypeVisitor& visitor) override {
        visitor.VisitB(*this);
    }
};

class PrintHelloVisitor : public TypeVisitor {
 public:
    virtual void VisitA(A& a) override {
        a.Hello();
    }

    virtual void VisitB(B& b)  override {
        b.Hello();
        b.Do(); // calling also the method that is not virtual
    }

    virtual ~PrintHelloVisitor() = default;
};

int main() {
    PrintHelloVisitor print_visitor;

    unique_ptr<A> a1 = make_unique<A>();
    a1->Accept(print_visitor);

    unique_ptr<A> b1 = make_unique<B>();
    b1->Accept(print_visitor);

    unique_ptr<B> b2 = make_unique<B>();
    b2->Accept(print_visitor);
}

Prints:

 A
 B
 Another method of B
 B
 Another method of B

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM