这种方式是否可以接受向向量添加unique_ptr?

Is this way acceptable for adding a unique_ptr to a vector?

本文关键字:添加 向量 unique ptr 方式 是否      更新时间:2023-10-16

假设我有一个盒子。它包含由Box制造的东西,并且只有Box。这些东西也是不可复制的。

还有一个所有权关系,所以我想使用unique_ptrs.(如果盒子走了,一切都随之而来。

#include <memory>
#include <vector>
class Thing {
friend class Box;
public:
~Thing() {/* delete thing */};
private:
Thing() {/* construct thing */};
Thing(const Thing& other) = delete;
Thing& operator=(Thing&& other) = delete;
};
class Box {
private:
std::vector<std::unique_ptr<Thing> > contents{};
public:
void makeThing() {
contents.push_back(nullptr);
contents.back().reset(new Thing()); // I can live with the 'new' keyword here.
}
};

我的问题是:通过推送nullptr然后将其重置为新对象来添加到向量是否不好? (除了new关键字,如果构造函数不抛出,这似乎没问题?

注意:我已经看到了克服私有构造函数和删除复制语义的替代方法。但是我上面写的东西有什么危险的问题吗?

注意 2:在这里明确说明它,因为它已经抓住了一些人:std::make_unique不起作用,因为 Thing 构造函数是私有的。

我会采用这种结构:

auto thing = std::unique_ptr<Thing>(new Thing());
contents.push_back(std::move(thing));

因为这更描述了你真正想要做的事情。您的Box对象创建一个Thing对象,将所有权传递给unique_ptr然后将该unique_ptr移动到contents

并且不要使用contents.emplace_back(new Thing());一个原因来确定它可能会泄漏。但是 - 恕我直言 - 更重要的部分是可读性和可维护性,如果你写contents.emplace_back(new Thing());那么你需要检查contents.emplace_back是否真的声称所有权(contents真的是std::vector<std::unique_ptr<Thing> >类型而不是std::vector<Thing *>std::move(thing)thing是一个unique_ptr很明显所有权被移动了。

您的解决方案的缺点是,在阅读contents.push_back(nullptr);时不清楚为什么要添加nullptr,您需要阅读下一行以了解原因。

我认为@t.niese有办法做到这一点。我想添加一个替代方案,稍微隐藏new,并可能阻止任何人使用emplace_back(new Thing)进行重构(可能会泄漏(。

#include <memory>
#include <vector>
class Thing {
friend class Box;
public:
~Thing() {/* delete thing */};
private:
Thing() {/* construct thing */};
Thing(const Thing& other) = delete;
Thing& operator=(Thing&& other) = delete;
static std::unique_ptr<Thing> allocate_thing() {
return std::unique_ptr<Thing>(new Thing);
}
};
class Box {
private:
std::vector<std::unique_ptr<Thing> > contents{};
public:
void makeThing() {
// DO NOT replace with emplace_back. Temporary prevents leekage in case of exception.
contents.push_back(Thing::allocate_thing());
}
};

我不喜欢这一点的是,人们可以想象Thing::allocate_thing()做了一些奇怪的魔术(事实并非如此(,因此这使代码更加混乱。

我进一步想说,也许你应该考虑一下你的设计。虽然尝试减少对Thing的访问是好的,但也许有更好的方法可以做到这一点(也许是匿名命名空间?当然,这不会阻止某人创建对象,如果他们可以在某个时候对向量的元素进行decltype(。如果您更笼统地提出您的问题,而不仅仅是要求对这个解决方案发表评论,也许您可以得到一些很好的推荐。

通常你会使用make_unique。但是由于Thing的构造函数是私有的,Box是朋友,所以你需要在Box中构造新的Thing,你不能使用std::make_unique。但是你的方法是...奇怪。

如果您不能使用make_unique则很容易产生泄漏的可能性。例如,要做的第一件事:

contents.emplace_back(new Thing()); // this can leak

将泄漏(如果矢量大小调整失败(

有一些解决方案可以解决这个问题,比如更长的解决方案:

contents.emplace_back(std::unique_ptr<Thing>(new Thing()));

或提前预订:

contents.reserve(contents.size() + 1); // this prevents the amortized constant complexity
// of `push_back`
// resulting in worst performance
contents.emplace_back(new Thing);

但问题仍然存在:编写泄漏代码很容易,正确的代码会做一些看起来不必要的额外事情,所以你需要注释和记录它们的目的,以便下一个程序员不会优化它们。

在我看来

,最好的解决方案是使构造函数可供make_unique
class Thing {
friend std::unique_ptr<Thing> std::make_unique<Thing>();
// ...
};
class Box {
// ...
void makeThing() {
contents.emplace_back(std::make_unique<Thing>());
}
};

由于结交make_unique朋友违背了私有构造函数的目的,因此我更喜欢 n314159 使用私有allocate_thing方法的解决方案。


感谢下面的所有评论者帮助使这篇文章正确和更好。

我一直在玩这个,只是注意到我降落在@n31459最终到达的地方。唯一的补充是它使用完美转发到Thing构造函数,因此如果您稍后添加更多构造函数,则只需要一个makeThing函数和工厂函数Thing

#include <memory>
#include <utility>
#include <vector>
class Thing {
public:
~Thing() = default;
private:
explicit Thing(int Value) : value(Value) {}
Thing(const Thing&) = delete;
Thing(Thing&&) noexcept = delete;
Thing& operator=(const Thing&) = delete;
Thing& operator=(Thing&&) noexcept = delete;
int value; // some data
template<class... Args>
static std::unique_ptr<Thing> make_unique(Args&&... args) {
return std::unique_ptr<Thing>{new Thing(std::forward<Args>(args)...)};
}
friend class Box;
};
class Box {
public:
template<class... Args>
void makeThing(Args&&... args) {
contents.emplace_back(Thing::make_unique(std::forward<Args>(args)...));
}
private:
std::vector<std::unique_ptr<Thing>> contents{};
};