考虑到所拥有对象的生命周期与其所有者相关联的常见情况,我可以通过两种方式之一使用唯一指针。 。
可分配:
class owner
{
std::unique_ptr<someObject> owned;
public:
owner()
{
owned=std::unique_ptr<someObject>(new someObject());
}
};
可以使用重置方法:
class owner
{
std::unique_ptr<someObject> owned;
public:
owner()
{
owned.reset(new someObject());
}
};
为了最佳实践,我应该选择一种形式而不是另一种形式吗?
编辑:对不起各位。我过度简化了这一点。堆分配发生在初始化方法中,而不是在构造函数中。因此,我无法使用初始化列表。
unique_ptr
的operator=
的文档:
将 r 指向的对象的所有权转移到 *this,就好像通过调用
随后从reset(r.release())
进行赋值一样。std::forward<E>(r.get_deleter())
您所需要的只是
reset
调用,因此直接调用它会更简单
执行此操作的正确方法(您没有列出)是使用
owned
: 的构造函数
owner() : owned(new someObject())
{}
除此之外,我更喜欢
reset
,因为在这种情况下您不会创建无用的中间实例(即使在机器级别上可能没有区别,因为优化器可以在那里做很多事情)。
简短回答:两种解决方案是等效的。选择哪一个取决于风格或你想要表达的内容。
详细:最好、最可取的解决方案是使用不同的设计:尽量避免任何生命周期状态。这可以通过立即使用新分配的堆实例来初始化
unique_ptr
来实现:
class owner
{
std::unique_ptr<someObject> owned;
public:
owner()
: owned{new someObject()}
{ }
};
为什么这是更好的选择?因为它整体上要么成功,要么失败。如果分配失败或者
someObject
的 ctor 爆炸,owner
的构造也会抛出异常。这意味着:您只能在完整、有效的状态下获得 owner
实例。作为额外的好处,没有中介参与。
但是,在某些情况下,您无法在初始化时创建。 当您的用例明确调用生命周期状态时尤其如此:有时可能需要将
owned
为空,并且仅将 someObject
稍后放入其中,由外部事件触发。
在这种情况下,就会出现问题:我们应该从
make_unique
分配还是应该从 reset()
分配。两者在功能上是等效的。两者都会安全地清理恰好位于 someObject
中的另一个 unique_ptr
实例。两者都会转移所有权并移动删除器。
reset()
意味着您更改或重新设置已经存在的东西。如果 unique_ptr
之前为空,则可能会产生误导。而且reset()
解决方案的好处是更短、更明确(你可以看到“新”)make_unique
在更抽象的层面上传达含义:“创建一个独特的someObject
实例并将其放置在这里”。有些人发现它更清晰、更精确,其他人则认为它隐藏了实际的分配。此外, make_unique
创建一个临时对象,并将其移动到目标中。优化器可以完全忽略它。一般来说,
make_unique
可以改变某些情况下的异常安全性。因此,通常建议养成使用make_unique
的习惯。在实践中,如果您创建多个唯一管理对象,这就会变得相关。
someFunction(make_unique<someObject>(arg1),
make_unique<someObject>(arg2));
如果其中一个角色发生故障,另一个角色始终会受到安全管理。但是,在这里讨论的情况下,我们只创建一个对象。那么如果出现异常怎么办?
reset(new someObject)
,则首先创建参数。如果这个问题发生了,很好,重置函数不会被调用。但是,如果
reset()
必须清理现有的其他实例,并且该实例的析构函数 抛出异常,那么我们就会遇到一个问题:
reset()
调用将因异常而留下,并且新的堆分配参数泄漏。
make_unique()
进行分配,这种特殊情况可以安全处理:如果旧实例的析构函数抛出异常,堆栈展开将调用
make_unique()
返回的临时对象的析构函数。