我应该分配或重置unique_ptr吗?

问题描述 投票:0回答:3

考虑到所拥有对象的生命周期与其所有者相关联的常见情况,我可以通过两种方式之一使用唯一指针。 。

可分配:

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());
    }
};

为了最佳实践,我应该选择一种形式而不是另一种形式吗?

编辑:对不起各位。我过度简化了这一点。堆分配发生在初始化方法中,而不是在构造函数中。因此,我无法使用初始化列表。

c++ c++11 smart-pointers unique-ptr
3个回答
55
投票

来自

unique_ptr
operator=
的文档:

将 r 指向的对象的所有权转移到 *this,就好像通过调用

reset(r.release())
随后从
std::forward<E>(r.get_deleter())
进行赋值一样。

您所需要的只是

reset
调用,因此直接调用它会更简单


19
投票

执行此操作的正确方法(您没有列出)是使用

owned
:

的构造函数
owner() : owned(new someObject())
{}

除此之外,我更喜欢

reset
,因为在这种情况下您不会创建无用的中间实例(即使在机器级别上可能没有区别,因为优化器可以在那里做很多事情)。


0
投票

简短回答:两种解决方案是等效的。选择哪一个取决于风格或你想要表达的内容。

详细:最好、最可取的解决方案是使用不同的设计:尽量避免任何生命周期状态。这可以通过立即使用新分配的堆实例来初始化

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()
     返回的临时对象的析构函数。
但这只是一个“主要是理论上的”论点。在实践中,有一种“社会契约”,目的是让正派的人“不造成破坏”。这是有充分理由的:在 dtor 调用上抛出对象会导致无尽的痛苦,并且基本上会危及处理任何类型“情况”的能力。

© www.soinside.com 2019 - 2024. All rights reserved.