64

Consider the following example:

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual void makeSound() {cout << "rawr" << endl;}
};

class Dog : public Animal
{
public:
    virtual void makeSound() {cout << "bark" << endl;}
};

int main()
{
    Animal animal;
    animal.makeSound();

    Dog dog;
    dog.makeSound();

    Animal badDog = Dog();
    badDog.makeSound();

    Animal* goodDog = new Dog();
    goodDog->makeSound();
}

The output is:

rawr
bark
rawr
bark

But I thought that surely the output should be "rawr bark bark bark". What's with the badDog?


Update: You may be interested in another question of mine.

Community
  • 1
  • 1
JnBrymn
  • 24,245
  • 28
  • 105
  • 147
  • 1
    I think you have some naming errors for your variables. – aioobe Dec 09 '10 at 22:24
  • 3
    The code doesn't even compile as is. And `void main()` ??? Ew... – Paul R Dec 09 '10 at 22:24
  • 2
    I can't understand why someone would downvote this question -- it's neither "unclear" nor "not useful". +1 to negate the downvote. – casablanca Dec 09 '10 at 22:27
  • +1 for the cute doggy metaphore. – Android Eve Dec 09 '10 at 22:28
  • 1
    The guideline "Make non-leaf classes abstract" would prevent this surprise. – Andy Thomas Dec 09 '10 at 22:28
  • 22
    And another +1 for posting a complete, minimal example that illustrates the problem. Which is exactly what we always ask for. – John Dibling Dec 09 '10 at 22:29
  • The -1 is for posting code that doesn't even compile, and failing to fix it even when this has been pointed out. – Paul R Dec 09 '10 at 22:33
  • oops... code should be fixed now. Sorry. (Now I want my point Paul!) – JnBrymn Dec 09 '10 at 22:34
  • @John: It's almost fixed. The correct return type of `main` is `int` (you can read more than you ever wanted to know about the `main` function in ["What is the proper declaration of main?"](http://stackoverflow.com/questions/4207134/what-is-the-proper-declaration-of-main/4207223#4207223) – James McNellis Dec 09 '10 at 22:35
  • 1
    @James - No. The *standard* return for main is int. An implementation is free to accept any signature and provide other entry points. If this was comp.lang.c++ where people get smacked for non-standard code and questions then you'd have a point. That isn't the SO way though. – Edward Strange Dec 09 '10 at 22:37
  • @Noah: That's been the SO way for as long as I've been here. Questions tagged only [c++] are assumed to be in Standard C++ with no platform-specific extensions. – John Dibling Dec 09 '10 at 22:39
  • 1
    Haha, I love the return sqrt(-1)! :) – Jesse Emond Dec 09 '10 at 22:40
  • @Noah: No. The `main` function "shall have a return type of type `int`, but otherwise its type is implementation defined." (N3225 3.6.1/2) – James McNellis Dec 09 '10 at 22:41
  • Though technically your comment is correct where you say "an implementation is free to accept any signature," since the return type of a nontemplate function is not part of its signature. :-) – James McNellis Dec 09 '10 at 22:47
  • @John Berryman. If you are struggling to see what the payoff is for learning concepts such as this in C++, please take a look at design patterns (at a later stage when your familiar with concepts such as pollymorphism). Most of the patterns are only possible due to the concepts you are currently trying to learn. Please see here for C++ implementations of patterns http://www.vincehuston.org/dp/. Also, if you want a good text book on this stuff please get http://www.amazon.com/Programming-Objects-Comparative-Presentation-Oriented/dp/0471268526. – Robben_Ford_Fan_boy Dec 09 '10 at 23:08
  • @David - The payoffs are obvious. I actually have a good understanding of polymorphism and its uses in design pattern, it's just that I come from a Java background and C++ treats values and references very different. – JnBrymn Dec 10 '10 at 03:43
  • Since you come from a Java background, I'd strongly recommend a good C++ textbook. C++ and Java differ on the most fundamental level: their object and memory models are completely and totally different and the idioms used in the two languages are likewise completely different. Introductory books can be tough to read as an experienced programmer because they are "boring" (yes, I know; I've been learning C# recently, and it's been a beating at times), but I'd argue it's very important, especially for C++, since it's very easy to write wrong, incorrect C++ code. – James McNellis Dec 10 '10 at 04:40
  • you should really rename Animal to Dinosaur :) –  Oct 31 '12 at 04:00
  • Nobody mentioned that it's leaking memory?? – Mauricio Sep 13 '18 at 20:48
  • I replied to your slide with some interesting complements : https://stackoverflow.com/questions/4405634/learning-c-returning-references-and-getting-around-slicing – Dev Jul 20 '19 at 11:29

3 Answers3

83

This is a problem called "slicing."

Dog() creates a Dog object. If you were to call Dog().makeSound(), it would print "bark" as you expect it to.

The problem is that you are initializing the badDog, which is an object of type Animal, with this Dog. Since the Animal can only contain an Animal and not anything derived from Animal, it takes the Animal part of the Dog and initializes itself with that.

The type of badDog is always Animal; it can never be anything else.

The only way you can get polymorphic behavior in C++ is using pointers (as you have demonstrated with your goodDog example) or using references.

A reference (e.g., Animal&) can refer to an object of any type derived from Animal and a pointer (e.g., Animal*) can point to an object of any type derived from Animal. A plain Animal, however, is always an Animal, nothing else.

Some languages like Java and C# have reference semantics, where variables are (in most cases) just references to objects, so given an Animal rex;, rex is really just a reference to some Animal, and rex = new Dog() makes rex refer to a new Dog object.

C++ doesn't work that way: variables don't refer to objects in C++, variables are objects. If you say rex = Dog() in C++, it copies a new Dog object into rex, and since rex is actually of type Animal, it gets sliced and just the Animal parts get copied. These are called value semantics, which are the default in C++. If you want reference semantics in C++, you need to explicitly use references or pointers (neither of these are the same as references in C# or Java, but they are more similar).

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 5
    I will add that I have grossly oversimplified the comparison between the type systems of Java and C# and C++. – James McNellis Dec 09 '10 at 22:44
  • Why c++ object assignment doesn't copy the vptr together with the rest members, to get the poloymorphism in variable level? – Ripley Jul 26 '13 at 07:03
11
 Animal badDog = Dog();
    ad.makeSound();

When you instantiate a Dog and assign it by-value to an Animal variable, you slice the object. Which basically means you strip off all the Dog-ness from badDog and make it in to the base class.

In order to use polymorphism with base classes, you must use either pointers or references.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
-1

You initialized badDog using the assignment operator. Thus Dog() was copied as Animal. The output of your program is correct. :)

Android Eve
  • 14,864
  • 26
  • 71
  • 96