Difference in Behavior While Using Dynamic_Cast with Reference and Pointers

Difference in behavior while using dynamic_cast with reference and pointers

Yes, this is correct behaviour. The reason is that you can have a null pointer, but not a null reference - any reference has to be bound to an object.

So when dynamic_cast for a pointer type fails it returns a null pointer and the caller can check for that, but when it fails for a reference type it can't return a null reference, so an exception is the only reasonable way to signal a problem.

When to use dynamic_cast of reference?

Basically if our object is allowed to be one of different types, we can dynamic_cast to a pointer so we can check if the cast succeeded:

void do_if_derived(Base& b) {
Derived* d = dynamic_cast<Derived*>(&b);
if (d) {
// do something
}
else {
// not a Derived, this is OK
}
}

but if our object has to be a single specific type, we can dynamic_cast to a reference and let the cast throw if it happens to be wrong:

void this_better_be_a_derived(Base& b)
{
Derived& d = dynamic_cast<Derived&>(b);
// do stuff with d
// will throw if, e.g. b is a DifferentDerived& instead
}

It's a matter of wanting to handle the failure case via a branch or via an exception.

Why is dynamic_cast'ing between pointers from different type hierarchies well defined?

The cast from C* to D* cannot be rejected by the compiler because dynamic_cast can cast not only "up" and "down" a class hierarchy, but also "sideways". For example, suppose we have

struct E : C, D { };
C* p = new E;
auto q = dynamic_cast<D*>(p);

Then q will point to the D subobject of the complete E object containing the C object that p points to.

This is specified in [expr.dynamic.cast]/(8.2).

Of course, a sufficiently smart compiler could still warn you, in some cases, that a dynamic_cast is guaranteed to fail (if it knows where the pointer comes from).

dynamic cast for references

dynamic_cast throws an exception on failure if used with references. To handle failure, catch the exception:

try {
XYZ& xyz = dynamic_cast<XYZ&>(abc);
}
catch (std::bad_cast& e) {
//handle error
}

Does dynamic_cast work even when there is no inheritance?

Sideways cast would be for example like this:

class Base1 { virtual ~Base1() = default; };
class Base2 { virtual ~Base2() = default; };
class Derived: public Base1, public Base2 {};

int main() {
Base1* p1 = new Derived;
Base2* p2 = dynamic_cast<Base2*>(p1);
}

Types Base1 and Base2 are unrelated to each other, but you can cast between the pointers because Derived inherits from both.

I'm not sure what did the original answerer mean by "cast [...] up another chain", but I guess they meant a situation where you would have a Base1* pointer and would cast to Base2 parent.

Regular cast vs. static_cast vs. dynamic_cast

static_cast

static_cast is used for cases where you basically want to reverse an implicit conversion, with a few restrictions and additions. static_cast performs no runtime checks. This should be used if you know that you refer to an object of a specific type, and thus a check would be unnecessary. Example:

void func(void *data) {
// Conversion from MyClass* -> void* is implicit
MyClass *c = static_cast<MyClass*>(data);
...
}

int main() {
MyClass c;
start_thread(&func, &c) // func(&c) will be called
.join();
}

In this example, you know that you passed a MyClass object, and thus there isn't any need for a runtime check to ensure this.

dynamic_cast

dynamic_cast is useful when you don't know what the dynamic type of the object is. It returns a null pointer if the object referred to doesn't contain the type casted to as a base class (when you cast to a reference, a bad_cast exception is thrown in that case).

if (JumpStm *j = dynamic_cast<JumpStm*>(&stm)) {
...
} else if (ExprStm *e = dynamic_cast<ExprStm*>(&stm)) {
...
}

You can not use dynamic_cast for downcast (casting to a derived class) if the argument type is not polymorphic. For example, the following code is not valid, because Base doesn't contain any virtual function:

struct Base { };
struct Derived : Base { };
int main() {
Derived d; Base *b = &d;
dynamic_cast<Derived*>(b); // Invalid
}

An "up-cast" (cast to the base class) is always valid with both static_cast and dynamic_cast, and also without any cast, as an "up-cast" is an implicit conversion (assuming the base class is accessible, i.e. it's a public inheritance).

Regular Cast

These casts are also called C-style cast. A C-style cast is basically identical to trying out a range of sequences of C++ casts, and taking the first C++ cast that works, without ever considering dynamic_cast. Needless to say, this is much more powerful as it combines all of const_cast, static_cast and reinterpret_cast, but it's also unsafe, because it does not use dynamic_cast.

In addition, C-style casts not only allow you to do this, but they also allow you to safely cast to a private base-class, while the "equivalent" static_cast sequence would give you a compile-time error for that.

Some people prefer C-style casts because of their brevity. I use them for numeric casts only, and use the appropriate C++ casts when user defined types are involved, as they provide stricter checking.

dynamic_cast against random pointers?

From this dynamic_cast reference:

dynamic_cast < new_type > ( expression )

...

expression - lvalue of a complete class type if new_type is a reference, prvalue of a pointer to complete class type if new_type is a pointer.

[Emphasis mine]

The complete class type is important here, as it means you can't really pass any generic pointer to dynamic_cast.

The type of expression must also be related to the new_type (i.e. a base-class, a child-class, or a sibling-class) or the behavior will be undefined.

If you use dynamic_cast with any "random pointer" you will have undefined behavior, and while a compiler might be able to warn you about it (though not always possible) still attempting to do something leading to UB is on you as the programmer.



Related Topics



Leave a reply



Submit