简体   繁体   中英

Store objects with the same base class in C++ <vector> by value (no pointers) by deleting the copy constructor etc

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

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM