QuickQuestion
QuickQuestion

Reputation: 13

Safe way to dynamic cast void to a type?

My C++ is a bit rusty and I don't remember everything in the standard.

I have a void*. In one specific function it is either a class that inherits Alpha or one that inherits Beta. Both base classes have virtual functions. However I can't seem to tell which is which

class Alpha {
public:
    virtual void Speak() { printf("A"); }
};
class Beta {
public:
    virtual void Speak() { printf("B"); }
};
int main(){
    //BAD CODE WILL PRINT TWICE
    void *p = new Alpha;
    Alpha*a = dynamic_cast<Alpha*>((Alpha*)p);
    Beta*b = dynamic_cast<Beta*>((Beta*)p);
    if(a)
        a->Speak();
    if(b)
        b->Speak();
    return 0;
}

How do I figure out which class is which? There are 100's of classes in this codebase that gets converted to void. Most of them inherit 5 base classes however I'm not eager to find out. Is the only solution inheriting from something like class Dummy {public: virtual void NoOp(){}}; and cast to Dummy before using dynamic cast? Is this safe? I'm hoping there's a better solution but I can't think of anything else.

Upvotes: 1

Views: 1429

Answers (5)

kylefinn
kylefinn

Reputation: 2403

I feel this could be explained more simply than what is said already.

Basically, casting from a raw void*, none of the "smarts" of dynamic_cast can apply because it has nothing to go on, it just gives you back the same pointer. You need to have some shared parent class type or something as your pointer type (*p) so it knows how to read the memory and determine actual type.

In your case, you are "lucking out" that the memory layout of Alpha and Beta is the same so calling speak on Beta* ends up calling Alpha::speak(). As others said this is UB and if the classes were more different you would likely seg-fault or corrupt the stack.

Upvotes: 0

Bathsheba
Bathsheba

Reputation: 234695

The only thing you can do with a void* pointer is to cast it back to exactly the same type as the pointer that was cast to void* in the first place. The behaviour on doing anything else is undefined.

What you could do in your case is define

class Base
{
    public:
    virtual ~Base() = default; // make me a polymorphic type and make 
                               // polymorphic delete safe at the same time.
};

and make this the base class for Alpha and Beta. Then pass a Base* pointer around rather than a void* one, and take your dynamic_casts directly on p.

Note further that if you declared virtual void Speak() = 0; in Base, then your code in main would become simply

int main(){ 
    Base* p = new Alpha;
    p->Speak();
    delete p; // ToDo - have a look at std::unique_ptr
}

As a rule of thumb, casts of any kind are undesirable.

Upvotes: 6

Tezirg
Tezirg

Reputation: 1639

If you use a C-style cast to convert to Alpha*, similar to a static_cast before using dynamic cast, then the dynamic cast as no effect. here your code runs because the two classes have the same interface but in reality this is undefined behaviour.

Usually, you want to use dynamic cast to upcast/downcast from/to a base class to/from it's derived class.

For example, if we add a base interface, then convert the void * pointer to this base class and then use dynamic cast to attempt Up-casting, the code works as expected and only print once.

#include <stdio.h>

class Speaker {
public:
  virtual void Speak() = 0;
};

class Alpha: public Speaker {
public:
  virtual void Speak() { printf("A"); }
};

class Beta: public Speaker {
public:
  virtual void Speak() { printf("B"); }
};

int main(){
  void *p = new Alpha;
  // Convert to base type, using static_cast                                    
  Speaker *s = static_cast<Speaker *>(p);
  // Attempt Upcasts                                                            
  Alpha*a = dynamic_cast<Alpha*>(s);
  Beta*b = dynamic_cast<Beta*>(s);

  // See if it worked                                                           
  if (a)
    a->Speak();
  if (b)
    b->Speak();
  return 0;
}

Outputs: A

Upvotes: 1

eerorika
eerorika

Reputation: 238341

You must know the type from which the void pointer was converted from. If you don't know the dynamic type, then you must create the void pointer from the pointer to base. If you don't know the type of the pointer which the void pointer was created from, then you cannot use the void pointer.

Given that the void pointer was converted from Alpha*, you can convert it back using a static cast:

auto a_ptr = static_cast<Alpha*>(p);

You can then use dynamic_cast to convert to a derived type.

if(auto d_ptr = dynamic_cast<DerivedAlpha*>(a_ptr)) {
    // do stuff with DerivedAlpha

In case the dynamic type isn't DerivedAlpha, the dynamic cast would safely return null. You cannot dynamic cast away from a type hierarchy. Since Alpha and Beta are not related by any inheritance structure, they cannot be dynamically casted mutually.

Upvotes: 0

Fran&#231;ois Andrieux
Fran&#231;ois Andrieux

Reputation: 29022

The expression Alpha*a = dynamic_cast<Alpha*>((Alpha*)p); first casts p to Alpha* with an explicit c style cast. Then, that resulting Alpha* is passed through dynamic_cast<Alpha*>. Using dynamic_cast<T*> on a T* pointer (a pointer of the same type as you are trying to cast to) will always provide the input pointer. It cannot be used to confirm that the pointer is valid. From cppreference for dynamic_cast<new_type>(expression) :

If the type of expression is exactly new_type or a less cv-qualified version of new_type, the result is the value of expression, with type new_type.

As a result, the code will always compile and run and the type system will not protect you. But the resulting behavior is undefined. In the case of Beta*b = dynamic_cast<Beta*>((Beta*)p); you tell the compiler to trust that p is a Beta* but this is not true. Dereferencing the resulting pointer is undefined behavior and dynamic_cast cannot protect you from this mistake.

If you try to remove the explicit type conversion, you will get a compiler error. dynamic_cast requires a pointer or reference to a complete type, and void is not a complete type. You will have to find a way to track the actual type pointed to yourself and explicitly convert p to that pointer type before using dynamic_cast. Though at that point, if you already know the type to cast to, it may no longer be necessary.

Consider using a common base type instead or maybe using std::variant or std::any if need be.

Upvotes: 1

Related Questions