c++ 我应该分配或重置unique_ptr吗?

z9gpfhce  于 2023-10-21  发布在  其他
关注(0)|答案(3)|浏览(99)

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

可以赋值:

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

从最佳实践的Angular 来看,我是否应该选择一种形式而不是另一种?

**对不起,伙计们。我把它简化了。堆分配发生在initialise方法中,而不是ctor中。因此,我不能使用初始化器列表。

2j4z5cfb

2j4z5cfb1#

the docs of unique_ptr 's operator=
将r指向的对象的所有权转移到 *this,就像调用reset(r.release()),然后从std::forward<E>(r.get_deleter())进行赋值一样。
您所需要的只是reset调用,因此直接调用它会更简单

wz8daaqr

wz8daaqr2#

正确的方法是使用owned的构造函数:

owner() : owned(new someObject())
{}

除此之外,我更喜欢reset,因为在这种情况下你不会创建一个无用的中间示例(即使在机器级别上可能没有区别,因为优化器可以在那里做很多事情)。

bkhjykvo

bkhjykvo3#

简答:两种解决方案是等效的。选择哪一个是风格的问题,或者你想表达什么。
详细内容:最好和最可取的解决方案是使用不同的设计:尽量避免任何生命周期状态。这可以通过使用新分配的堆示例立即 * 初始化 * 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));

如果一个的ctor失败了,另一个总是安全地管理。
然而,在这里讨论的情况下,我们只创建一个对象。那么,如果发生例外情况会发生什么呢?

  • 如果我们使用reset(new someObject),则首先创建参数。如果这个失败了,很好,重置函数不会被调用。但是,如果reset()必须清理一个现有的其他示例,并且该示例的 * 析构函数 * 抛出,那么我们就有问题了:reset()调用将因异常而离开,并且新分配的堆参数泄漏。
  • 另一方面,如果我们从make_unique()赋值,这种特殊情况可以安全地处理:如果旧示例的析构函数抛出,堆栈展开将调用make_unique()返回的临时示例的析构函数。

但这是一个“理论上的”论点。在实践中,有一种“社会契约”,目的是体面的人不要破坏。这是有道理的在dtor调用中抛出的对象可能会导致无尽的痛苦,并且基本上会危及处理任何类型的“情况”的能力。

相关问题