Как построить `std :: vector` объектов, которые имеют в качестве переменной-члена уникальную переменную? - PullRequest
0 голосов
/ 16 апреля 2020

Я хочу иметь unique_ptr в качестве переменной класса для поддержки полиморфизма. У меня построен класс, но я не могу использовать конструктор std::vector, потому что конструктор копирования std::unique_ptr явно удален. Вот абстрактный пример:

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

class Animal {

protected:
    std::string noise = "None";
public:
    Animal() = default;

    virtual std::string getNoise() {
        return noise;
    }

};

class Duck : public Animal {
public:
    Duck() {
        noise = "Quack!";
    }
};

class Dog : public Animal {
public:
    Dog() {
        noise = "Woof!";
    }
};

typedef std::unique_ptr<Animal> AnimalPtr;


class Zoo {
public:
    AnimalPtr animalPtr;

    explicit Zoo(AnimalPtr animalPtr) : animalPtr(std::move(animalPtr)){};

    explicit Zoo(const Animal& animal) : animalPtr(std::make_unique<Animal>(animal)){};

    const AnimalPtr &getAnimalPtr() const {
        return animalPtr;
    }

};

int main() {

    Zoo zoo1((Dog()));
    Zoo zoo2((Duck()));

    std::vector<Zoo> zoos = {zoo1, zoo2}; // error, Call to implicitly-deleted copy constructor of 'const Zoo'

    return 0;
};

Я мог бы решить эту проблему, используя вместо этого std::shared_ptr, но что-то подсказывает мне, что это не правильная причина для разрешения совместного владения. Итак, мой вопрос, как правильно решить эту проблему? (т.е. чтобы я мог построить std::vector из animal с.

Ответы [ 4 ]

2 голосов
/ 16 апреля 2020

Начиная с C ++ 11, std :: vector может прекрасно разместить объект, который не может быть скопирован. Однако не все методы вектора могут быть использованы. В частности, конструктор initializer_list, который вы вызываете для инициализации вектора, не допускаю, что он является интуитивно понятным, я допускаю. В текущем стандарте intializer_list всегда работает по копии, а не по ходу, это может измениться позже, я полагаю. Вот подпись конструктора:

vector(std::initializer_list<T> init, const Allocator& alloc = Allocator());

В любом случае, не оборачивая zoo1 и zoo2 на std::move, вы попросили сделать копию zoo1 и zoo2 в любом случае для сборки std::initializer_object

Однако вы по-прежнему можете использовать конструктор по умолчанию, для которого не требуется копировать значение, а затем выполнить некоторые операции push_back s, например

std::vector<Zoo> zoos;
zoos.push_back(std::move(zoo1));
zoos.push_back(std::move(zoo2));

Как упоминалось в rustyx ответ на вопрос также может использовать emplace_back для непосредственного построения вашего Zoo объекта из любого Animal на месте внутри вектора вместо перемещения уже построенного Zoo внутри

1 голос
/ 16 апреля 2020
class Zoo {
public:
AnimalPtr animalPtr;

explicit Zoo(AnimalPtr animalPtr) : animalPtr(std::move(animalPtr)) {};

explicit Zoo(const Animal& animal) : animalPtr(std::make_unique<Animal>(animal)) {};

Zoo(const Zoo& obj) : animalPtr(make_unique<Animal>(*obj.animalPtr)) {}

const AnimalPtr &getAnimalPtr() const {
    return animalPtr;
}

};

Если вы объявите конструктор копирования, как упомянуто выше, то файл успешно скомпилируется, оставив оператор ниже:

std::vector<Zoo> zoos = { zoo1, zoo2 };

0 голосов
/ 16 апреля 2020

Поскольку владельцем указателей на животных является класс Zoo (один уникальный владелец). На самом деле не имеет смысла «копировать конструкцию» вектора зоопарков (вам придется переместить экземпляры зоопарка в вектор). Почему бы просто не объявить:

std::vector<Zoo*> zoos = {&zoo1, &zoo2};
0 голосов
/ 16 апреля 2020

Проблема в том, что initializer-list в векторной конструкции делает копии, потому что initializer-list передает элементы по const-ссылке.

Вы можете обойти это, добавив элементы вручную:

std::vector<Zoo> zoos;
zoos.emplace_back(Dog());
zoos.emplace_back(Duck());
...