繁体   English   中英

使用智能指针制作多态容器的正确方法是什么?

[英]What is the correct way to make a polymorphic container using smart pointers?

我想做一个维护多态容器的 C++ class 。 例如,兽医可以持有一份目前正在接受治疗的宠物的清单。 如果我们使用原始指针,我们可以如下定义一个 vet。

class Vet{
    std::vector<Pet*> pets;
public:
    void addPet(Pet* pet);  
};

我们可以如下添加宠物。

Vet vet;
vet.addPet(new Dog{});
vet.addPet(new Cat{});

在这种情况下,应该让 Vet class 的析构函数负责删除 pets 中维护的动态分配的pets 为了避免这种情况,我想使用智能指针。 但是,在我能够正确和干净地实现此类代码之前,需要澄清一些问题。 在以下问题上需要帮助。 提前致谢。

  1. 我应该使用std::unique_ptr还是std::shared_ptr 或者在什么情况下我应该使用std::unique_ptrstd::shared_ptr 还是我应该 go 回到使用原始指针?
  2. 如果我选择使用std::unique_ptr addPet方法及其实现的方法签名是什么?
  • 选择 1-1)
void addPet(std::unique_ptr<Pet> pet){
    pets.push_back(std::move(pet));
}
  • 选择 1-2)
void addPet(std::unique_ptr<Pet>& pet){
    pets.push_back(std::move(pet));
}

仅当我按以下方式构建宠物时,此选择才有效。

std::unique_ptr<Pet> dog = std::make_unique<Dog>();
vet.addPet(dog);
  • 选择 1-3)
void addPet(PetType pet){
    if(pet==PetType::Dog) pets.push_back(std::make_unique<Dog>());
    //
}
  1. 如果我选择使用std::shared_ptr ```addPet''' 方法的方法签名及其实现是什么?
  • 选择 2-1)
void addPet(std::shared_ptr<Pet> pet){
    pets.push_back(std::move(pet));
}
  • 选择 2-2)
void addPet(const std::shared_ptr<Pet>& pet){
    pets.push_back(pet);
}

假设你真的想在外面创建宠物,最好的方法是unique_ptr并按值传递它,如下所示:

void addPet(std::unique_ptr<Pet> pet);

unique_ptr因为您告诉读者只有一个所有者。

按价值计算,因为这需要您在调用addPet时使用临时或显式移动 unique_ptr 。 在外部非常明确地表明您正在移动/转移所有权。

例如,您可以这样做

auto pet = std::make_unique<Dog>();
vet.addPet(std::move(pet)); // move is required, making it explicit that ownership is transferred

或者

vet.addPet(std::make_unique<Dog>());

如果您在addPet中接受对unique_ptr的引用,则调用者将不知道在调用addPet后提供的参数是否仍然有效。

是一篇关于这个主题的好文章。

使用unique_ptr shared_ptr用于共享所有权,但您希望vet拥有它包含的宠物,即不共享所有权。

unique_ptr不需要出现在界面上。 添加猫狗的公共界面可以是这样的:

class vet {
    public:
       void add_dog(const std::string& name);
       void add_cat(const std::string& name);
};

当您想要转移所有权时,使用unique_ptr作为 function 参数。 没有必要先让调用者拥有宠物,然后让他们将所有权转移给vet ....除非那是你想要的,然后使用std::unique_ptr<Pet>作为参数(没有参考,但按价值)。

如果没有必要,不要实施已经可用的资源控制。 您不必让用户构建 object,这增加了您不必向用户公开您在内部使用的数据结构的好处。

class Vet {
  private:
    // Make sure Pet::~Pet() is virtual!
    std::vector<std::unique_ptr<Pet>> pets;
  public:
    template <typename Species, typename... Args>
    void addPet(Args...&& args) {
      pets.emplace_back(std::make_unique<Species>(std::forward<Args>(args)...));
    }
};

// ...

vet.addPet<Cat>(3, "black"); // assuming Cat::Cat(unsigned age, std::string color)

但是请注意,从语义上讲,兽医并不拥有宠物,他们会照顾它一段时间,然后释放宠物。 兽医在完成治疗后摧毁宠物将是一个糟糕的兽医。

无论如何,如果您想获得宠物,您可以轻松添加类似的成员 function:

Pet& getPet(std::size_t const i) {
  return *pets[i];
}
template <typename Species>
Species& get(std::size_t const i) {
  return static_cast<Species&>(getPet(i));
  // maybe consider dynamic_cast, here
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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