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?

1
  • 2
    nice example code! screaming cows makes for an interesting read :) much better than foo and bar Commented Jun 16, 2010 at 1:52

3 Answers 3

12

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

Sign up to request clarification or add additional context in comments.

7 Comments

To clarify: Your vector needs to hold pointers because it suffers from the slicing problem when you hold actual objects in it.
So did my Cow get degraded to a mere Animal?
@wrong: Kinda -- the Animal part of your Cow was copied into the vector.
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.
Heh: the boost::ptr_vector<> tutorial even uses the 'Animal' hierarchy for its examples (no Cows, though): boost.org/doc/libs/1_35_0/libs/ptr_container/doc/tutorial.html
|
2

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);
}

3 Comments

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, 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.
Indeed, it would be quite cruel to first torture the animals and then simply forgetting about them... release the poor creatures!
2

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... */
}

1 Comment

@Martin: Actually, &Cow() is illegal in standard C++ because the address-operator can only be applied to lvalues. And even if some perverted compiler allows it, the temporary cow does not exist in the line barn.tortureAnimals(); anymore!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.