I am learning C++ inheritance, so I tried this code by creating a Base class dynamically and made a downcast to its Derived class (obviously its not valid to downcast) in order
As @Mark Ransom has suggested in the comments, I looked the post When does invoking a member function on a null instance result in undefined behavior?. Then I came up with my own solution.
This behavior of invoking a non created object's method is not a matter of fact with the parent
class nor child
class or downcast or inheritance . I had statically told to call the child::who()
by downcasting, the compiler calls child::who()
without caring about the object type.
But how the
who()
method is called since no object is created forchild
class?
The method which I tried to invoke doesn't have any member variable so there is no need to dereference this
pointer. It just calls who()
, the same behavior is true if the child
class pointer points to NULL
also.
child *c = NULL;
c->who();
It prints I am child
even though c
points to NULL
. Because no need to dereference this
pointer. Lets have a situation where who()
method contains a member variable x
as below.
class child : public parent{
private:
int x;
public:
void who(){
x = 10;
cout << "I am child " << x;
}
};
child *c = NULL;
c->who()
Now it causes Segmentation fault
because in order to use x
the compiler have to dereference this
pointer for x as (*this).x
. Since this
points to NULL
dereferencing will cause Segmentation fault
. This is clearly an undefined behavior.
Quite simply, at the point that you invoke c->who()
, the static type of c (i.e. the type the compiler knows for c) is child*
and the method who
is non virtual. So the compiler emits the instructions for a call to the address of child::who
.
The compiler does not (and, in general, how could it?) keep track of the true type of the object that would be pointed to by c
at runtime.
If you had any members in child
that are not in parent
, accessing those members in child::who
would have produced an out-of-bounds access, which might cause a SIGSEGV, or other unpleasantness.
Finally, as to whether your observed behavior is guaranteed by the standard, I tend to agree with @P45Imminent: Both parent
and child
satisfy the requirements for POD and child
has no non-static data members. Consequently the runtime layout of parent
and child
objects is required to be indistinguishable per the standard (at least as far as methods of parent
and child
are concerned -- perhaps child
and parent
could have different amounts of padding at the end?). So the line from 9.3.1/2 quoted in one of the comments does not apply IMHO. I'd love to hear from folks more knowledgeable if this assumption on layout is not supported by the standard.
The reason your code behaves this way is probably because the compiler doesn't check the actual type of your object (it isn't required to unless your function is virtual
); it just calls child::who
because you told it to. That said, your code is certainly suspect.
You are statically downcasting the base class pointer to a derived class pointer, which not type-safe. C++ will not prevent you from doing this; it is up to you to ensure that your pointer really does point to an object of the derived type. Dereferencing the pointer if it does not refer to an object of the derived type is potentially undefined behaviour. You are lucky your code even prints anything at all.
You need to make sure your base class function who
is virtual
, otherwise the function call will not behave polymorphically. Keep in mind, once you add virtual
to your code, you will certainly be invoking undefined behaviour, because you are illegally downcasting to an invalid type. You can safely downcast using dynamic_cast, which will return a nullptr
if the object is not of the specified type.
You should also generally have a virtual
destructor in your base class, so that your object can be delete
d polymorphically. Speaking of which, you also need to make sure to delete
your dynamically allocated object at some point. By manually calling new
and delete
, it is very easy to leak memory like this, even if you know that you need to call delete
. std::unique_ptr and std::shared_ptr were added to the standard library in C++11 to address these concerns. Use these instead of new
and delete
in all but the most low-level code.
To summarize, here is how I suggest your code should look:
#include <iostream>
#include <memory>
class parent {
public:
virtual ~parent() {}
virtual void who() {
std::cout << "I am parent";
}
};
class child : public parent {
public:
void who() override {
std::cout << "I am child";
}
};
int main() {
auto p = std::make_unique<parent>();
auto c = dynamic_cast<child*>(p.get());
if (c) // will obviously test false in this case
{
c->who();
}
}
What you are seeing is Undefined Behavior at work.
Your functions are non-virtual, they are simply member functions of the type you've told the compiler the pointer points to.
child *c = (child*)new parent;
This is a c-style cast that strong-arms the compiler into the belief that the pointer c definitely points to something that is a child.
Thus, when you call c->who()
, you are specifically calling child::who
, because the pointer is a pointer-to-child.
The reason nothing terrible happens and you see "I am child" is because you don't try to dereference that pointer or make use of any of the child-specific fields that your pointed-to-instance doesn't actually have. So you get away with it.
Polymorphism doesn't work like that. Interestingly your C-style cast from parent*
to child*
works because the classes don't have a v-table or anything else other than the function who
. So the address of who
must be the same as the address of the class
itself.
parent *p = (parent*)new child();
will make more sense, but even then, p->who()
would only call the child class function if you mark the function who
virtual
in the parent
class, which you haven't done.
first of all, this is no valid downcast. The real type of new parent()
indeed is parent
and not child
. Downcasting is only allowed if the real (also called dynamic) type is child
but the pointing object is a parent
at the moment.
The other way around would make more sense. If you create a child
and assign it to a parent
pointer, this would be fine. But even then: Unless who
is virtual
the static type instead of the dynamic type decides which function is called.
Example:
class parent{
public:
void who(){
cout << "I am parent";
}
~virtual parent() {}; // important to have that in class hierarchies where you might use a child that is assigned to a parent pointer!
};
class child : public parent{
public:
void who(){
cout << "I am child";
}
};
int main(){
parent *c = (parent *)new child();
c->who(); // output would still be parent because who() is not virtual
delete c; // important! never forget delete!
return 0;
}
If you use, on the other hand,
virtual void who();
then, the output would be "I am child" like you would expect.