Reputation: 4309
I read in a C++ book that you can use dynamic_cast
to downcast a pointer to a base object to a derived object pointer, if the object it points to actually is that of the derived type. The format is dynamic_cast<Derived*>(basePointer)
. Furthermore, I read on this website that dynamic_cast
should return a null pointer if the object it points to cannot be converted to the derived class type.
However, I recently tried having a pointer to a plain object with a virtual function downcast to an object that's derived from a different class, in order to call one of that class' functions. To my surprise, it worked:
#include <iostream>
using namespace std;
class Object {
public:
virtual ~Object () {}
};
class Base {
public:
virtual void doSomething () {
cout << "Done something!" << endl;
}
};
class Derived: public Base {
public:
virtual void doSomething () {
cout << "You done goofed!" << endl;
}
void printFoo () {
cout << "Foo" << endl;
}
private:
int x;
};
int main () {
Object o;
Object* p = &o;
if(dynamic_cast<Derived*>(p))
cout << "Yep, baby is derived!" << endl;
else
cout << "Isn't derived." << endl;
dynamic_cast<Derived*>(p)->printFoo();
return 0;
}
Here, Base
is a base class type designed to be used polymorphically (that is, it contains a virtual function), and Derived
is a class type derived from Base
. Object
is just a plain class that is not related to either class, but its destructor is made virtual so that it can be used polymorphically. I'll explain the purpose of Derived::x
in a moment. In the main function, an Object
is created and a pointer to Object
is assigned to its address. It checks whether Derived
is a descendant of that pointer, and prints whether it is or it isn't. Then it casts that pointer to a pointer of type Derived
, and Derived
's printFoo
function is called.
This shouldn't work, but it does. When I run it, it displays "Isn't derived," so it clearly understands that Object
is not derived from Base
, but then it prints "Foo" on the screen and exits without error. However, if I add the line x = 1;
to the printFoo
function, it exits with a Segmentation fault, as it's trying to assign a variable which doesn't exist in that object. Furthermore, this only seems to work with non-virtual functions. If I try to call the virtual function doSomething
, for instance, I get the Segmentation fault before "You done goofed!" is ever printed out.
What's going on here? Shouldn't dynamic_cast<Derived*>(p)
return a null pointer, so that trying to call printFoo
from that would automatically cause an error? Why is that working when it shouldn't?
Upvotes: 5
Views: 2099
Reputation: 308490
You've already established that dynamic_cast
is returning a NULL pointer, so the real question is why function calls on a NULL object pointer appear to work in some cases but not others.
In ALL of these cases you're getting undefined behavior. Just remember that undefined doesn't always mean crash - sometimes you get perfectly reasonable results. You just can't rely on it.
Here's an explanation based on what's probably going on, but there are no guarantees it will work the same way tomorrow, much less when you change the optimization settings or get a new version of the compiler.
printFoo
isn't virtual, so there's no need to use a vtable to access it. doSomething
is virtual so it does need the vtable. A NULL pointer doesn't have a vtable so calling doSomething
blows up immediately.
printFoo
doesn't use any of the members of the object until you add the x = 1
line to it. As long as the compiler doesn't generate any code that accesses the this
pointer, it's likely to work fine.
Upvotes: 2
Reputation: 3525
You're accessing a null
pointer without using any actual object state, which is safe though meaningless.
If you change Derived::printFoo() to the following, you'll actually be in "undefined behavior territory", and will very likely segfault:
void printFoo () {
cout << "Foo: " << x << endl;
}
For what it's worth, using references instead of pointers will make this throw an exception if you'd rather propagate the failure outward than test and deal with it locally:
dynamic_cast<Derived &>(*p).printFoo(); // kaboom! throws std::bad_cast
Upvotes: 1
Reputation: 7625
It works because
dynamic_cast<Derived*>(p)->printFoo();
returns a null
pointer as expected, but the null
pointer is of type Derived. When it calls printFoo
, it works because printFoo
does not use any member variable, in which case it would refer to this
for using that variable, and would crash for dereferencing null
pointer. Since no member variable is used, no reference to this
, no problem( that's why it gives segfault when u use x
in the method).
It is same as following simple code:
class A{
public:
int x;
void f1(){}
void f2(){x=1;}
}
A* x=nulptr;
x->f1(); // will work
x->f2(); // UB
Upvotes: 2