5

I'm a Java programmer and recently started studying C++. I'm confused by something.

I understand that in C++, to achieve polymorphic behavior you have to use either pointers or references. For example, consider a class Shape with an implemented method getArea(). It has several subclasses, each overriding getArea() differently. Than consider the following function:

void printArea(Shape* shape){
    cout << shape->getArea();
}

The function calls the correct getArea() implementation, based on the concrete Shape the pointer points to.

This works the same:

void printArea(Shape& shape){
    cout << shape.getArea();
}

However, the following method does not work polymorphicaly:

void printArea(Shape shape){
    cout << shape.getArea();
}

Doesn't matter what concrete kind of Shape is passed in the function, the same getArea() implementation is called: the default one in Shape.

I want to understand the technical reasoning behind this. Why does polymorphism work with pointers and references, but not with normal variables? (And I suppose this is true not only for function parameters, but for anything).

Please explain the technical reasons for this behavior, to help me understand.

Aviv Cohn
  • 15,543
  • 25
  • 68
  • 131
  • This might be helpful. http://stackoverflow.com/questions/7516545/why-is-object-slice-needed-in-c-why-it-is-allowed-for-more-bugs – R Sahu Oct 03 '14 at 23:51
  • 5
    It might also be helpful to know that in Java all objects are actually pointers to real objects. So, if you have class Shape, then "Shape a=b" only copies the pointer to the actual object, internally, and increments its internal reference count. When all references to an object are gone, it becomes subject to Java's garbage collection. In C++, you get to play with the actual objects. Copy them around, etc. This is why polymorphism only works with pointers (or references, which are mostly syntactical sugar for pointers). – Sam Varshavchik Oct 04 '14 at 00:30
  • The function only receives a `Shape`. If you pass a subtype (or any other type) it gets converted into (assigned to) a `Shape`. – Galik Oct 04 '14 at 01:19

4 Answers4

4

The answer is copy semantics.

When you pass an object by value in C++, e.g. printArea(Shape shape) a copy is made of the object you pass. And if you pass a derived class to this function, all that's copied is the base class Shape. If you think about it, there's no way the compiler could do anything else.

Shape shapeCopy = circle;

shapeCopy was declared as a Shape, not a Circle, so all the compiler can do is construct a copy of the Shape part of the object.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
  • I see. And this applies to simple assignment too, not only to parameter passing, right? For example in the following code: `Shape s; if(something) s = Rectangle(5, 5); else s = Circle(5); cout << s.getArea();` Whatever is assigned into `s` is simply reduced to a `Shape` (doesn't matter if it's a `Rectangle` or a `Circle`, because the value is copied? – Aviv Cohn Oct 03 '14 at 23:59
  • Correct, passing by value causes a copy to be made, but it's all about making a copy, not specifically about passing a parameter. – Jonathan Potter Oct 04 '14 at 00:00
  • Still, not sure why the compiler can't assign a `Circle` instance to a `Shape` variable - i.e. why it has to 'slice' it before assigning it to the variable. One assumption is that it's because of memory: the compiler allocated the variable a suitable amount of memory for a `Shape`, which doesn't necessarily suit a `Circle` object. So it 'reduces' the `Circle` to the definition of a `Shape`. Is that correct? – Aviv Cohn Oct 04 '14 at 09:29
  • 1
    Basically correct, yes. A `Shape` is a `Shape`, a `Circle` is a `Shape` plus other stuff. When you declare an object as a `Shape` that's all you get. When you declare a pointer as a `Shape*` then you get *at least* a `Shape` but potentially more, depending on the object it points to. – Jonathan Potter Oct 04 '14 at 13:23
  • And a pointer `Class*` can point to a `DerivedClass`, because all addresses take the same amount of memory anyway? – Aviv Cohn Oct 04 '14 at 14:54
  • A pointer `Shape*` just points to a chunk of memory that you've told the compiler is a `Shape`; whether or not that memory has the extra bits of a `Circle` tacked on as well, the pointer is still the same. – Jonathan Potter Oct 04 '14 at 19:49
1

When void printArea(Shape shape) is called, your object is copied into a brand new Shape on the stack. The subclass parts are not copied. This is known as object slicing. If the base-class object is not legit (e.g. it has pure virtual functions in it), you can't even declare or call this function. That's "pass by value" semantics; a copy of the passed-in object is supplied.

When void printArea(Shape& shape) is called, a reference to your Circle or Rectangle object is passed. (Specifically, a reference to the Shape part of that object. You can't access the Circle- or Square-specific members without casting. But virtual functions work correctly, of course.) That's "pass by reference" semantics; a reference to the original object is passed in.

StilesCrisis
  • 15,972
  • 4
  • 39
  • 62
  • Strictly speaking, you really are passing a reference to a `Shape` object in the latter case. The interesting question is whether this `Shape` object is most-derived or whether it is a subobject of some larger object. The "finding out what your environment is" is the thing that your platform needs to implement by some suitable kind of magic. – Kerrek SB Oct 03 '14 at 23:55
  • Added a parenthetical aside in the body to clarify. Thanks! – StilesCrisis Oct 03 '14 at 23:57
  • Thanks for answering. Still, I'm not sure why the compiler can't assign a Circle object to a Shape variable - i.e. why it has to 'slice' it before assigning it to the variable. One assumption I have is that it's because of memory: the compiler allocated the variable a suitable amount of memory for a `Shape`, which doesn't necessarily suit a Circle object. So it 'reduces' the Circle to the definition of a `Shape`. Is that correct? – Aviv Cohn Oct 04 '14 at 09:31
1

You're talking about run-time polymorphism.

This is the idea that an object can be of a class derived from the *statically known class, so that calls to virtual member functions in the statically known class, end up in derived class implementations.

With a pointer or reference the most derived class can be different from (more specific than) the statically known class. But with a direct variable the statically known class is the most derived class. So there's no room for run-time polymorphism, except in calls that end up causing calls from base class member functions (in a base class member function the statically known type is that base class, different from the most derived class).


*) Statically known class means known to the compiler without any analysis, the declared class.
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • I do not know if everybody at this level understands what you mean with "statically known class". Additionally, the OP states a Java background, so the C++ value/copy semantics might be unknown. From a slightly different point of view, one could talk about the type of the object that actually exists in memory, and the type the compiler sees when looking at the function parameter. – dyp Oct 04 '14 at 00:01
0

Basically, when you pass an object by value it's copied to an object of the destination type - at that point it is an object of the destination type, so there's nothing to polymorph (is that even a word?).

To use your example, void printArea(Shape shape);, inside the printArea() function the parameter shape is a Shape object, regardless of what object was used at the call site.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760