0

I have read (here https://stackoverflow.com/a/18351550/1474291) that it is possible to store objects of derived classes that inherit the same base class in a <vector>. To prevent Object Slicing I need to delete the move constructor, the copy constructor and the copy assignment.

NOTE: I am interested in storing the object by value and not as pointers.

I tried to compile the following code with Visual C++ 2013 and MinGW (GCC 4.8.2 and 4.9.1) but the code fails to compile. I want to do that both in C++11 by using "delete", "default" and the older way.

What is the correct way to implement that? (Do actually C++ compilers support "delete" and "default" properly yet?)

Here is my example code:

#include <iostream>
#include <vector>

using namespace std;

#if __cplusplus > 199711L
    #define CPP11
#endif


class Animal {
public:
    Animal() {
        cout << "Making animal:";
    }
    virtual ~Animal() {
        cout << "Send the animal home!";
    }

#ifdef CPP11
public:
    Animal(Animal&&) = delete;
    Animal(Animal const&) = delete;
    Animal& operator=(Animal&) = delete;
#else // C++98
private:
    Animal(Animal const&);
    Animal& operator=(Animal&);
#endif // CPP11

public:
    virtual void speak() {
        cout << "I am an animal!";
    }
};

class Dog : public Animal {
public:
    Dog() {
        cout << "Making dog:";
    }

    virtual ~Dog() {
        cout << "Send the dog home!";
    }

#ifdef CPP11
public:
    Dog(Dog&&) = default;
    Dog(Dog const&) = default;
    Dog& operator=(Dog&) = default;
#else // C++98
private:
    Dog(Dog const&);
    Dog& operator=(Dog&);
#endif // CPP11

    virtual void speak() {
        cout << "I am a dog!";
    }
};

class Cat : public Animal{
public:
    Cat() {
        cout << "Making cat";
    }

    virtual ~Cat() {
        cout << "Sending the cat home!";
    }

#ifdef CPP11
public:
    Cat(Cat&&) = default;
    Cat(Cat const&) = default;
    Cat& operator=(Cat&) = default;
#else // C++98
private:
    Cat(Cat const&);
    Cat& operator=(Cat&);
#endif // CPP11

    virtual void speak() {
        cout << "I am a cag!";
    }
};

int main()
{
    vector<Animal> animals;

    for (int i = 0; 10 > i; i++) {
        Dog dog;
        animals.push_back(dog);
        Cat cat;
        animals.push_back(cat);
    }

#ifdef CPP11
    for (Animal& animal: animals) {
        animal.speak();
    }
#else
    for (std::vector<Animal>::iterator currentAnimal = animals.begin();
         currentAnimal != animals.end();
         ++currentAnimal) {
        currentAnimal->speak();
    }

#endif // CPP11

    return 0;
}
Community
  • 1
  • 1
Zingam
  • 4,498
  • 6
  • 28
  • 48
  • 5
    Where did you read that? You must have misunderstood something. – juanchopanza Jul 21 '14 at 09:16
  • 2
    You can't. Objects don't work this way. – n. m. could be an AI Jul 21 '14 at 09:17
  • @juanchopanza http://stackoverflow.com/a/18351550/1474291 – Zingam Jul 21 '14 at 09:18
  • Store by value implies that the value is with the type. So the stored objects are either *really* with the type, or they're sliced. – Mine Jul 21 '14 at 09:19
  • You misunderstood something. That answer is telling you how to prevent slicing by forcing a compilation error. – juanchopanza Jul 21 '14 at 09:19
  • What you might mean are base-class pointers. What you can do is `Animal *animal1 = new Dog();` and `Animal *animal2 = new Cat();` and then you can store `animal1` and `animal2` in the same vector **but** they are not pointing to `Dog` and `Cat` but instead to `Animal`. – a_guest Jul 21 '14 at 09:20
  • @a_guest I know that! Thank you. – Zingam Jul 21 '14 at 09:22
  • 4
    " To prevent Object Slicing I need to delete the move constructor, the copy constructor and the copy assignment". This is about as useful as cutting one's head to prevent headaches. Removing constructors makes the object uncopyable. `std::vector` requiers its members to be copyable. – n. m. could be an AI Jul 21 '14 at 09:24
  • 1
    @n.m.: I think vector only requires copyability if you want to have elements. It should be OK to have *empty* vectors of non-copyable types. Then there won't be any slicing. – Kerrek SB Jul 21 '14 at 09:26
  • 1
    A very obvious reason why you can't do this: A vector stores objects consecutively in memory, and an underlying assumption is therefore that they are all of the same size. Whilst in your example the objects may well be the same size, this is generally not the case. – marko Jul 21 '14 at 09:28
  • In fact you often do want to prevent all copying, but not if you want to store objects in a vector by value. "Storing by value" means storing a copy. – n. m. could be an AI Jul 21 '14 at 09:33
  • I have outlined a solution that would work, but this has to do away with inheritance at the level of the types actually stored in the vector. It works by wrapping the polymorphic type. – juanchopanza Jul 21 '14 at 12:09

2 Answers2

2

The point I think you are missing is that the container needs to set aside a specific amount of memory for every element it contains. So the container's elements are of fixed size.

Now you want to stuff objects of varying sizes into those fixed size boxes. Do you see where this is heading?

You are going to get slicing whenever you try to stuff a derived object that is larger than its base class into a variable (read container element) designed to hold base class sized objects.

Galik
  • 47,303
  • 4
  • 80
  • 117
  • 1
    It is not just a matter of object size. The container can only store objects of the base type. It cannot store anything else. – juanchopanza Jul 21 '14 at 11:49
1

You cannot avoid object slicing when putting objects of a derived type into a vector of the base. The question you refer to explains how to avoid object slicing by making it impossible to copy objects of the relevant type. This means instead of object slicing you get a compilation error.

That aside, one possible way to implement a "polymorphic" type that can be stored by value is by implementing one single type, whose implementation can be set at runtime. Something along these lines:

class Animal
{
 public:
  void talk() const { impl_->talk(); }
  // implement copy, assignment, move copy, move assignment
  // implement constructor allowing to specify the implementation

 private:
  std::unique_ptr<AnimalImpl> impl_;
};

struct AnimalImpl
{
  virtual void talk() const = 0;
  virtual ~AninalImpl() {}
};

struct Elephant : AnimalImpl
{
  void talk() const override { std::cout << "I am an elephant\n"; }
};

Then you provide a means of constructing Animal objects with different underlying implementations. This allows you to store different kinds of Animal in an std::vector<Animal>.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 2
    Down-voter, is there something wrong with the answer? I'll be glad to fix it if that is the case. – juanchopanza Jul 21 '14 at 10:10
  • That is interesting! Thank you! How would I implement this without unique_ptr for older compilers and without boost? – Zingam Jul 21 '14 at 18:07