views:

106

answers:

3

Let's say I have a base class Animal from which a class Cow inherits, and a Barn class containing an Animal vector, and let's say the Animal class has a virtual function scream(), which Cow overrides.

With the following code:

Animal.h

#ifndef _ANIMAL_H
#define _ANIMAL_H
#include <iostream>
using namespace std;

class Animal {
public:
    Animal() {};
    virtual void scream() {cout << "aaaAAAAAAAAAAGHHHHHHHHHH!!! ahhh..." << endl;}
};

#endif  /* _ANIMAL_H */

Cow.h

#ifndef _COW_H
#define _COW_H

#include "Animal.h"

class Cow: public Animal {
public:
    Cow() {}
    void scream() {cout << "MOOooooOOOOOOOO!!!" << endl;}
};

#endif  /* _COW_H */

Barn.h

#ifndef _BARN_H
#define _BARN_H

#include "Animal.h"
#include <vector>

class Barn {
    std::vector<Animal> animals;

public:
    Barn() {}
    void insertAnimal(Animal animal) {animals.push_back(animal);}
    void tortureAnimals() {
        for(int a = 0; a < animals.size(); a++)
            animals[a].scream();
    }
};

#endif  /* _BARN_H */

and finally main.cpp

#include <stdlib.h>
#include "Barn.h"
#include "Cow.h"
#include "Chicken.h"

/*
 * 
 */
int main(int argc, char** argv) {
    Barn barn;
    barn.insertAnimal(Cow());
    barn.tortureAnimals();
    return (EXIT_SUCCESS);
}

I get this output:

aaaAAAAAAAAAAGHHHHHHHHHH!!! ahhh...

How should I code this to get MOOooooOOOOOOOO!!! (and whatever other classes inheriting Animal wants scream() to be) instead?

+12  A: 

A std::vector<Animal> can only contain Animal objects, not Cow objects.

Here's what happens when you say barn.insertAnimal(Cow());:

  1. A temporary object of type Cow is created by the evaluation of Cow().
  2. The parameter animal is copy-constructed from that temporary cow since you chose to pass it by value. This animal is a copy of the Animal-part of the temporary cow. This is called object-slicing.
  3. The next element in the vector is copy-constructed from the parameter animal. Now you already have one cow and two additional animals, but you only wanted one cow!
  4. The parameter animal is destroyed because insertBarn returns.
  5. The temporary cow is destroyed because evaluation has reached the semicolon at the end of the line (to be more precise: evaluation of the full-expression has completed).

What is the lesson here? Don't pass animals by value, and don't store animals by value. Runtime polymorphism requires a level of indirection. You probably want a std::vector<Animal*> or std::vector<shared_ptr<Animal> > or boost::ptr_vector<Animal>.

FredOverflow
To clarify: Your vector needs to hold pointers because it suffers from the slicing problem when you hold actual objects in it.
Billy ONeal
So did my `Cow` get degraded to a mere `Animal`?
wrongusername
@wrong: Kinda -- the `Animal` part of your `Cow` was *copied* into the vector.
FredOverflow
I would advice against a vector of pointers. Rather use a boost:ptr_vector<Animal> this is a container specifically designed to hold pointers and manage their lifespan. Alternatively use a vector of shared pointers to Animal.
Martin York
Heh: the `boost::ptr_vector<>` tutorial even uses the 'Animal' hierarchy for its examples (no Cows, though): http://www.boost.org/doc/libs/1_35_0/libs/ptr_container/doc/tutorial.html
Michael Burr
@FredOverflow: I have corrected *copy of the `Cow`-part of the temporary cow* to *copy of the `Animal`-part of the temporary cow*. I hope you don't mind.
David Rodríguez - dribeas
`This is called object-slicing.` This is even better than tortureAnimals() LOL!
AOI Karasu
@David: Thanks!
FredOverflow
+2  A: 

A slight modification that stores pointers to animals should help:

#ifndef _BARN_H
#define _BARN_H

#include "Animal.h"
#include <vector>

class Barn {
    std::vector<Animal *> animals;

public:
    Barn() {}
    void insertAnimal(Animal *animal) {animals.push_back(animal);}
    void tortureAnimals() {
        for(int a = 0; a < animals.size(); a++)
            animals[a]->scream();
    }
};


int main(int argc, char** argv) {
    Barn barn;
    barn.insertAnimal(new Cow());
    barn.tortureAnimals();

    // should clean up barn contents here...

    return (EXIT_SUCCESS);
}
Amardeep
Err.. you have to delete those. Perhaps `boost::ptr_vector` or `std::vector<boost::shared_ptr>`, or you'll have to have a for loop deleting the things....
Billy ONeal
@Billy ONeal, yes I was editing in a comment to that effect. The boost pointers would be a better approach, but required more editing to the example. I felt this would get the point across for how to deploy polymorphism.
Amardeep
Indeed, it would be quite cruel to first torture the animals and then simply forgetting about them... release the poor creatures!
FredOverflow
+2  A: 

You're doing pass-by-value, so Cow is being made into Animal.

Using Animal * instead works, though this has memory leaks:

class Barn {
    std::vector<Animal *> animals;

    void insertAnimal(Animal *animal) {animals.push_back(animal);}

    void tortureAnimals() {
        for(int a = 0; a < animals.size(); a++)
            animals[a]->scream();
    }
};

int main(int argc, char** argv) {
    Barn barn;
    Cow *c = new Cow();
    barn.insertAnimal(c);
    barn.tortureAnimals();
    /* delete them here... */
}
egrunin
@Martin: Actually, `` anymore!
FredOverflow
Sorry mistake. I miss read the code.
Martin York
@Stephen, @FredOverflow: good catch, fixed.
egrunin
@egrunin : now _that_ is a memory leak ;)
Stephen