简短回答:这两种解决方案是等效的。选择哪个取决于个人风格或者你想要表达的意思。
详细解释:最好且最可取的解决方案是使用不同的设计:尽量避免使用任何生命周期状态。这可以通过立即使用新分配的堆实例对unique_ptr进行初始化来实现:
class owner
{
std::unique_ptr<someObject> owned;
public:
owner()
: owned{new someObject()}
{ }
};
为什么这样更可取呢?因为它要么成功,要么失败。如果分配失败或者
someObject
的构造函数出错,
owner
的构造也会抛出异常。这意味着:你只能获得一个完整且有效的
owner
实例。作为额外的好处,没有中介参与其中。
然而,有些情况下你无法在初始化时创建。特别是当你的使用场景明确要求生命周期状态时:可能存在这样一种情况,你有时需要
owned
为空,并且只有在外部事件触发时才将
someObject
放入其中。
在这种情况下,问题就出现了,我们应该使用
make_unique
赋值还是使用
reset()
。从功能上来说,两者是等价的。两者都可以安全地清理另一个位于
unique_ptr
中的
someObject
实例。两者都会转移所有权并将删除器一同移动。
- 使用
reset()
意味着你要对已经存在的东西进行更改或重新设置。在unique_ptr
之前为空的情况下,这可能会引起误导。此外,reset()
的解决方案更短,更明确(你可以看到"new")。
- 使用赋值形式的
make_unique
在更抽象的层面上传达了意义:"创建一个唯一的someObject
实例并将其放在这里"。有些人认为这更清晰、更精确,而其他人则认为它隐藏了实际的分配。此外,make_unique
创建了一个临时对象,然后将其移动到目标位置。优化器可以完全省略这一步。
异常安全性
一般来说,make_unique
在某些情况下可以改变异常安全性。因此,通常建议养成使用make_unique
的习惯。实际上,这在创建多个受唯一管理的对象时变得相关。
someFunction(make_unique<someObject>(arg1),
make_unique<someObject>(arg2));
如果其中一个构造函数失败,另一个构造函数总是能够被安全地管理。
然而,在这里讨论的情况下,我们只创建一个对象。那么在出现异常的情况下会发生什么呢?
- 如果我们使用 `reset(new someObject)`,参数会首先被创建。如果这个过程出错,没关系,`reset` 函数不会被调用。但是,如果 `reset()` 需要清理一个已存在的实例,并且那个实例的析构函数抛出异常,那么我们就有了一个问题:`reset()` 调用会被异常中断,新分配的堆内存参数会泄漏。
- 另一方面,如果我们从 `make_unique()` 进行赋值,这种特殊情况会被安全处理:如果旧实例的析构函数抛出异常,栈展开将调用 `make_unique()` 返回的临时对象的析构函数。
但是,这只是一个大部分是理论的论点。在实践中,有一种“社会契约”,即正派的人们在析构过程中不会抛出异常。这是有充分理由的:在析构函数调用时抛出异常的对象会带来无尽的痛苦,基本上会危及处理任何“情况”的能力。