views:

247

answers:

10

Hi,

can you explain me why:

int main (int argc, char * const argv[]) {
    Parent* p = new Child();
    p->Method();
    return 0;
}

prints "Child::Method()", and this:

int main (int argc, char * const argv[]) {
    Parent p = *(new Child());
    p.Method();
    return 0;
}

prints "Parent::Method()"?

Classes:

class Parent {
public:
    void virtual Method() {
     std::cout << "Parent::Method()";
    }
};

class Child : public Parent {
public:
    void Method() {
     std::cout << "Child::Method()";
    }
};

Thanks, Etam.

+9  A: 

In the first case you call the actual object is of Child class:

Parent* p = new Child(); // you new'ed Child class
p->Method(); // and a method of a Child class object is getting called

that's why Child::Method() is called. In the second case you copy the Child class object onto the Parent class object:

Parent p = *(new Child()); // you new'ed Child, then allocated a separate Parent object on stack and copied onto it
p.Method(); // now you have a Parent object and its method is called

and Parent::Method() is called.

sharptooth
You've commented about what happens, which is what the OP states. Can you explain why it happens?
quamrana
That's because the virtual method is called depending on the actual object type, not the pointer type. In the first case the object is of Child class type, in the second one - of Parent class type.
sharptooth
@sharptooth: Yes, in the first case there is a virtual method call through a pointer, but in the second case there is no virtual method call, only a direct call to `Parent::Method()`
quamrana
Sure, and Parent::Method() is called. What's the question?
sharptooth
@sharptooth: The question is **why** is Parent::Method() called. I'm sure the answer is that its not a call to a virtual method, but a direct call.
quamrana
Yes, in this case the call is not virtual. But even if it was virtual still Parent::Method() would be called for a Parent class object.
sharptooth
+2  A: 

In your second example, slicing occurs: the Child-instance is converted to a Parent-instance, which has no vtable entry for the Child-method. The Child-method is "sliced" off.

jp
I originally thought it was slicing, but I don't think this is the mechanism. Its simply not a polymorphic call.
quamrana
As far as I know, slicing occurs when an instance of a derived class is copied as a base. Usually occurs when not accepting references or pointers as function arguments, but this is basically the same thing.Correct me if I'm wrong though.
jp
@quamrana: It's not a polymorphic call because Parent isn't a derived type. A Child is created via new. A Parent is created on the stack and then set equal to the Child (i.e. the Child is sliced down to a Parent). A method is then called in the Parent. Oh, and the memory from newing the Child has been leaked.
Michael Kohne
@Michael: Its not a polymorphic call because pointers (or references) are not involved, and not because of slicing. I'm pretty sure that the v-table would be copied across, but its not used because C++ is sure that the type is a Parent and it doesn't have to go through the v-table for the method call.
quamrana
+1  A: 

In the first case, you've got a 'Parent' pointer on a 'Child' object, which is somehow generic, since 'Child' inherits from 'Parent'. Hence the overloaded 'Child' method is called.

In the second case, you make an implicit cast of a 'Child' instance to the type 'Parent', hence you're calling the method on a 'Parent' object.

Hope this helps.

Vinzz
+11  A: 

Your second code copies a Child object into a Parent variable. By a process called slicing it loses all information specific to Child (i.e. all private fields partial to Child) and, as a consequence, all virtual method information associated with it.

Also, both your codes leak memory (but I guess you know this).

You can use references, though. E.g.:

Child c;
Parent& p = c;
p.Method(); // Prints "Child::Method"
Konrad Rudolph
I'm sure that slicing is not the cause of the symptoms in this case.
quamrana
Any answer to this question must contain the conceptual explanation of how polymorphism applies only to pointers and references, and in the second case 'p' is neither.By talking about slicing you're not providing the 'real' answer. It is a side-effect of what's happening, but doesn't answer the original question.
psychotik
I'm with @konrad. The real reason is not because you don't have a pointer or reference. But the real reason is that because the object isn't of type `Child`. And this is is caused by slicing.
Johannes Schaub - litb
A: 

In your first example you call Method through a pointer:

p->Method();

In this case 'p' is a pointer to a Parent, and C++ knows that Parent has a v-table, so it uses that to find the actual method to call, which in this case as you state is Child::Method() because when C++ finds the v-table, it finds the v-table of the 'new' Child instance.

In the second example you call Method on an instance:

p.Method();

In this case 'p' is an instance of Parent and C++ assumes that it knows that the exact type is indeed 'Parent' and so it calls Parent::Method() without going through any v-tables.

I've just checked this with VS2008 and the above is actually what happens and there's no slicing, however, I think that slicing does occur, but you would only see it in the second case if you did:

Parent& q=p;
q.Method();

Then I see: Parent::Method() being printed. q.Method() must be a virtual call, but it can only find the v-table for Parent.

quamrana
+3  A: 

Virtual behaviour is only available when the virtual function is called via a pointer or a reference. When you say:

Parent p = *(new Child());
p.Method();

you have an actual Parent object, so Parent's method will always be called, no matter what you assign to p.

anon
@Neil: virtual methods will be called just fine without a pointer or reference. Declare a Child on the stack and it's virtual methods will be called just spiffy.
Michael Kohne
A virtual methos will be called, but you won't get virtual behaviour.
anon
@Michael: Yes virtual methods will be called but as Neil said (I am just rephrasing), You won't get polymorphic behavior because there won't be a lookup to vtable involved in this case.
Aamir
A: 

In the first case, the type of the object pointed by pointer p is of 'Child' type. Since child is overriding the virtual method defined in the base class, child's method is called. In the second case, you have copied the child object into a parent object. Here the object slicing occurs and the type of the resultant object is of type 'Parent'. Hence when you call the method, parent's method is called.

Naveen
+1  A: 

Polymorphism in C++ is only possible with pointers. The differentiation between the static and the dynamic type (in your first example p is of static type Parent*, but of dynamic type Child*, and this enables C++ to call the derived class' method) does not come into play with non-pointers.

Thomas
“Polymorphism in C++ is only possible with pointers.” … and references. Which are *not* pointers.
Konrad Rudolph
Yes, you're right. Let's say "with reference semantics".
Thomas
+3  A: 

What happens if you do this?

int main (int argc, char * const argv[]) {
    Parent &p = *(new Child());
    p.Method();
    return 0;
}

That has the same "syntactic" effect (p doesn't require dereferencing to use it), but the semantic effect is now completely different because you're no longer copying part of a Child into a Parent.

Daniel Earwicker
quamrana
Konrad Rudolph
@Konrad: Can we leave memory leaks out of this for the moment?
quamrana
Johannes Schaub - litb
@litb: Just to be clear, I’m taking issue with the usage of `new` here, which allocates heap space. But since it’s only a temporary, the address is lost and can *never* be retrieved in a standards compliant manner. How will it be freed?
Konrad Rudolph
@Konrad - I agree, it's not realistic code - it's just the original code with the smallest possible change made to it to illustrate the point. To answer your specific question, the above code *could* allow the address to be found - just have the `Child` class provide its own `new` operator that adds each new instance to a list, allowing them to be reclaimed at some later time, and a matching custom `delete` that does nothing. (Not something I'd encourage anyone to do, however.)
Daniel Earwicker
@Earwicker why so freaking complicated? Just do it like @Konrad did. I don't see anything wrong with just doing `delete `.
Johannes Schaub - litb
@Konrad, the objects `new` allocates are not temporaries. They are objects that end lifetime only when delete are called upon them. The line in which he dereferences and binds to a reference also doesn't copy. The address is thus preserved. (Of course, `Parent` needs to have a virtual destructor to be able to delete through a pointer to its class).
Johannes Schaub - litb
@litb - I was referring (and I assume Konrad also) to the expression `*(new Child());` and retaining the address regardless of where it appears.
Daniel Earwicker
@litb - and "why so freaking complicated?" - I can only repeat that I wouldn't recommend anyone doing that! I was just answering the specific question in Konrad's comment: in the event that the pointer is dereferenced into a temporary and then sliced, how could the address be retained?
Daniel Earwicker
It couldn't unless you add it to a list - but there is no slicing happening in your answer. I assumed @Konrad claimed that your `*(new Child());` was creating a temporary (he said that your code leaks and calling delete on it is non-standard since it's a temporary), which it doesn't and isn't - i think you agree with me on that :) I just wanted to keep this clear for readers of the discussion
Johannes Schaub - litb
+2  A: 

Your first case is simple. An instance of Child is created and asigned to p. So calling p->Method() calls Child::Method().

In the second case, four things happen:

  1. An instance of the Child class, identified by a compiler-assigned temporary variable, is created.
  2. An instance of of the Parent class, identified by the variable p is created.
  3. The copy constructor Parent::Parent(Parent&) is called when p is instantiated to copy the Parent 'slice' of the state of the Child instance to p. Note that if you don't define this copy constructor then the compiler creates it for you.
  4. You call Method() on p, which is an instance of Parent.

Try explicitly defining the copy constructor and you'll see that it is called.

Your possible confusion is probably because the assignments (=) in the two examples do different things. In the first example its simply setting one pointer equal to another, and there is only one object. In the second there are no pointers, so you're assigning (slicing) value. This invokes the copy constructor, and you get two objects (a Child and a Parent). The fact that the compiler is creating an invisible temporary variable doesn't help in understanding whats going on. You might check-out this article on copy constructors.

This is an easy mistake to make if you're used to languages like Java or C# where basically everything is a reference (aka a pointer).

As others have said, polymorphism only works with pointers and references, and these are not used in your second example.

Andy Johnson