【问题标题】:How to construct a `std::vector` of objects which have a `unique_ptr` as member variable?如何构造具有“unique_ptr”作为成员变量的对象的“std::vector”?
【发布时间】:2020-07-29 11:51:00
【问题描述】:

我想要一个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::vectoranimals。

【问题讨论】:

标签: c++ polymorphism unique-ptr


【解决方案1】:
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&lt;Zoo&gt; zoos = { zoo1, zoo2 };

【讨论】:

  • 我想这就是我一直在寻找的答案。但它会被视为“黑客”吗?
  • 我不这么认为。所以只要测试一下这是否安全,我为向量创建了另一个容器,并使用 emplace_back 添加了相同的 zoo1 和 zoo2 对象。对于这两个类,ctor 都被调用了一次。如果 ctor 会被调用两次,这将是一个问题/黑客。它或多或少与您的明确 Zoo(const Animal& animal) 的 ctor 相同。
【解决方案2】:

自 C++11 起,std::vector 可以完美容纳无法复制的对象。然而,并非向量的所有方法都可以使用。特别是您正在调用的 initializer_list 构造函数来初始化您的向量,我承认,不允许它们是反直觉的。在当前标准中intializer_list 总是通过复制而不是移动工作,我想这可能会在以后改变。这是构造函数签名:

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

无论如何,通过不将zoo1zoo2 包装为std::move,您要求它复制zoo1zoo2 以构建std::initializer_object

但是你仍然可以使用不需要值可复制的默认构造函数,然后做一些push_backs,比如

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

正如rustyx 回答中提到的,还可以使用emplace_back 直接从向量内的任何Animal 构建您的Zoo 对象,而不是将已经构建的Zoo 移动到内部

【讨论】:

    【解决方案3】:

    因为动物指针的所有权是 Zoo 类(一个唯一的所有者)。 “复制构造” Zoos 的向量并没有真正意义(您必须将 Zoo 实例移动到向量中)。 为什么不简单地声明:

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

    【讨论】:

      【解决方案4】:

      问题在于向量构造中的initializer-list 会生成副本,因为初始化器列表通过常量引用传递元素。

      您可以通过手动添加元素来绕过它:

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

      【讨论】:

      • 我很欣赏这是一种解决方法(尽管我不确定您为什么使用emplace_back 而不是push_back,我会查一下)。但是,与其盲目使用 shared_ptr,或者强迫我的用户以特定方式构造 std::vector,我想知道是否有“正确”的方法来解决这个问题。如果答案是否定的,我很乐意使用此解决方案。感谢您的回答。
      • 我认为没有正确的方法。 braced-init-list 语法看起来简洁明了,但不会像我解释的那样工作,因为它想要制作副本并且Zoo 是不可复制的。切换到shared_ptr 将使braced-init-list 版本看起来可以工作,但它会通过在幕后执行不必要的复制来工作(即效率低下)。选择是您的 - 效率(使用 unique_ptr)或方便(使用 shared_ptr)。
      • 感谢您的建议。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-03
      • 1970-01-01
      • 1970-01-01
      • 2015-08-18
      • 1970-01-01
      • 2012-06-05
      • 1970-01-01
      相关资源
      最近更新 更多