1

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 to make this dynamically created Base object to be pointed by a Derived class pointer. But when I call a method who() through this Derived pointer it invokes the Derived class method instead of Base class method.

According to my assumption no object is created for the Derived class, then how could it be possible to invoke the non created Derived class object's method instead of actually created Base class object?

I googled for this phenomenon but couldn't find a clear and crispy explanation for invoking a non created Derived class object's method. If it is according to the standard then explain me how it works. I know the story will be different if I make the method virtual but the method used here is not virtual.

class parent{
    public:
        void who(){
            cout << "I am parent";
        }
};

class child : public parent{
    public:
        void who(){
            cout << "I am child";
        }
};

int main(){
    child *c = (child *)new parent();
    c->who();
    return 0;
}

The output is I am child but I expected I am parent

Edit:: I didn't freed up the memory in the above code and made an invalid downcast because I just wanted to see what happens. So just explain this behavior of invoking methods only.

jblixr
  • 1,345
  • 13
  • 34
  • 2
    The behaviour is undefined. Anything can happen. It's the same undefined story even if you make the function virtual. – molbdnilo Mar 17 '16 at 15:00
  • @molbdnilo: I don't think it is undefined in this particular case. See my very nice answer. – P45 Imminent Mar 17 '16 at 15:04
  • 2
    @P45Imminent - the behavior is undefined. The fact that you can come up with a rational explanation of the observed behavior doesn't change that. "Undefined behavior" means that the language definition doesn't tell you what happens; nothing more. – Pete Becker Mar 17 '16 at 15:08
  • I *think* the classes are layout compatible, so it's not UB. – P45 Imminent Mar 17 '16 at 15:10
  • @molbdnilo But if make virtual it invokes the parent method correctly. Is it also undefined? – jblixr Mar 17 '16 at 15:24
  • 1
    @jblixr You can't demonstrate the absense of undefined behaviour by observing behaviour. "It seems to do what I expect" is one example of undefined behaviour. It might do something else on a different machine, or compiled differently, or when the tide is low at dusk and a black crow caws nearby. – molbdnilo Mar 17 '16 at 15:30
  • @jblixr one of the most insidious aspects of undefined behavior is that it can appear to do something that is easily explainable, or even look correct. But since it isn't guaranteed, the next version of the compiler could make everything go splat. Your cast of a `parent` pointer to a `child` pointer invokes undefined behavior, period. – Mark Ransom Mar 17 '16 at 15:54
  • 2
    You might get some insight from http://stackoverflow.com/a/3826144/5987. It's not the same question at all, but similar mechanics are in place. – Mark Ransom Mar 17 '16 at 16:00
  • 1
    §9.3.1/2 Nonstatic member functions / 2. *If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.* – Captain Giraffe Mar 17 '16 at 16:23
  • @P45Imminent: Layout compatibility doesn't prevent UB. See http://stackoverflow.com/questions/21956354/can-i-legally-reinterpret-cast-between-layout-compatible-standard-layout-types – Christian Hackl Mar 17 '16 at 16:35
  • @MarkRansom Really helpful, I didn't think of dereferencing this pointer until reading your link. Since my function doesn't contain any member variable and statically called the child method so the child function is invoked without creating any instance for the child. Did I get the key point correctly? – jblixr Mar 17 '16 at 18:35
  • Yes, you got the point. I also hope you got the point of not relying on undefined behavior. – Mark Ransom Mar 17 '16 at 20:35

6 Answers6

2

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 deleted 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();
    }
}
Joseph Thomson
  • 9,888
  • 1
  • 34
  • 38
  • I will follow your suggestions but a child object is not even created then how it invokes child method? – jblixr Mar 17 '16 at 15:28
  • @jblixr Because, as I mentioned (I edited; sorry if you didn't see), the compiler is not required to check the actual type of the object. As you mention, adding `virtual` makes it behave how you expect, but adding `virtual` would most definitely make your code illegal, because, as I also mentioned, you cannot use objects of base type via a reference to a derived type! You can, however, do the inverse (access derived via base). – Joseph Thomson Mar 17 '16 at 15:32
  • @jblixr Edited again for clarity. – Joseph Thomson Mar 17 '16 at 15:42
2

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.

kfsone
  • 23,617
  • 2
  • 42
  • 74
1

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.

P45 Imminent
  • 8,319
  • 4
  • 35
  • 78
  • Even its non sense but how child method is invoked because no object is created for child class – jblixr Mar 17 '16 at 14:55
  • @jblixr you may want to read about v-tables and how objects are laid out in memory to understand why this happens to give a sensible result. – CompuChip Mar 17 '16 at 15:05
  • @jvlixr Doesn't matter since sizeof(parent) must be non-zero (standard insists on it), but sizeof(child) + sizeof(parent) = sizeof(parent). – P45 Imminent Mar 17 '16 at 15:05
  • @P45Imminent You had said a valid point, a bit slow for me to understand what you had said so the delay to respond. Is this standard or your assumption? – jblixr Mar 17 '16 at 15:19
  • My sizeof thing is standard. I think the standard insists that this must always work, but only for an edge case like this. – P45 Imminent Mar 17 '16 at 15:23
  • 1
    But please don't take the fact that it works in this edge case as any reason why you should do this in practice. It's a terrible idea. – P45 Imminent Mar 17 '16 at 15:29
1

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.

IceFire
  • 4,016
  • 2
  • 31
  • 51
  • But how child method is invoked? Because no object is anywhere created for child – jblixr Mar 17 '16 at 14:59
  • It works because neither class has a vtable and there's only one function; the address of that function must be the same as the address of the class. – P45 Imminent Mar 17 '16 at 15:00
  • Since your code is malwritten, there is no guarantee for anything. But the static type decides in such cases, most frequently also in code with errors. Sometimes it might even be possible to write `child* c = 0x12345678; c->who();` and you would get the output because the static type (child) is given. Of course, this kind of code must never be used. – IceFire Mar 17 '16 at 15:01
  • Disagree. The code is well-defined for this particular edge case. – P45 Imminent Mar 17 '16 at 15:02
  • Do you mean because no attribute is used? This is not enough. – IceFire Mar 17 '16 at 15:02
  • 1
    @P45Imminent no, it's not well defined. It happens to fail in a way that you find easy to understand, but it's not guaranteed to fail in that way on every compiler or in the future. The comments to the question have a quote from the standard that makes it very explicit. – Mark Ransom Mar 18 '16 at 20:23
  • Also, putting 'virtual' to who() lets the code dump at least in Ideone. Making a well-written code fail by adding 'virtual' seems highly unlikely. However, like always: standard wins – IceFire Mar 18 '16 at 20:34
0

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 for child 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.

Community
  • 1
  • 1
jblixr
  • 1,345
  • 13
  • 34
  • Actually they're both undefined behavior, it's just that in one case "undefined" turned out to be innocuous. I can't stress enough that you should *always* avoid undefined behavior, even when it appears to work, since it might stop working in the future for no apparent reason. – Mark Ransom Mar 18 '16 at 20:29
0

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.

Always Confused
  • 470
  • 4
  • 7