2012-12-13 128 views

回答

15

答案应该是从[unique.ptr.single.assign]移动分配的标准规范/ 2显而易见的:

影响:所有权转移从u*this仿佛调用reset(u.release())随后从std::forward<D>(u.get_deleter())转让。

明显移动分配与reset(u.release())不一样,因为它做了一些额外的事情。

的附加效果是很重要的,没有它你可以自定义删除器未定义行为:

#include <cstdlib> 
#include <memory> 

struct deleter 
{ 
    bool use_free; 
    template<typename T> 
    void operator()(T* p) const 
    { 
     if (use_free) 
     { 
     p->~T(); 
     std::free(p); 
     } 
     else 
     delete p; 
    } 
}; 

int main() 
{ 
    std::unique_ptr<int, deleter> p1((int*)std::malloc(sizeof(int)), deleter{true}); 
    std::unique_ptr<int, deleter> p2; 
    std::unique_ptr<int, deleter> p3; 

    p2 = std::move(p1); // OK 

    p3.reset(p2.release()); // UNDEFINED BEHAVIOUR! 
} 
5

例如,第一个能够警告您是否存在析构函数不匹配。此外,release()是一个非常危险的功能,你的微不足道的例子是正确的,但许多其他用途不是。最好永远不要使用这个功能。

+0

你能举个例子,为什么它很危险? – Ali

+0

它在哪里写入我可以调用的标准,例如'move()'后的'reset()'在'p2'上? – Ali

+3

@Ali由于'reset'没有任何先决条件,因此可以随时调用它,因为任何移动都指定为将移动的对象置于** unsdefined但有效的**状态,这使得不使用任何函数先决条件总是有效的。它只是依赖于某些先决条件(比如指针是'nullptr',但是甚至可以通过标准保证'std :: unique_ptr'),这需要你首先检查相应的条件。移动不会以任何方式使对象失效,这是一个常见的误解。 –

-2

我想,第二个版本可能不是异常安全的。它等效于:

auto __tmp = p2.release(); 
p1.reset(__tmp); 

因此,如果调用std::unique_ptr::reset抛出(这可能是这种情况,如果管理对象的缺失抛出),那么你有这将不会永远摧毁一个unreferred对象。在移动分配的情况下,std::unique_ptr可以(也应该)等待实际移动,直到p1的原始对象被正确销毁。

但是请注意,如果托管对象的析构函数可能抛出(几乎在所有情况下都是错误的),或者如果使用可能抛出的自定义删除器,这只是一个问题。所以在实践中,两个代码片段之间通常没有任何行为差异。


编辑:最终乔纳森在他的评论中指出,该定制删除是由标准要求不扔,这的确使得std::unique_ptr::reset件不可思议的事/不符合的投掷。但他也指出,还有一个区别,即只有一个移动任务也会移动任何定制的删除者,他也为此写了一个答案。


但是无视实际产生的行为,两者之间存在巨大的概念差异。如果移动分配合适,则然后执行移动分配,并尽量不要通过其他代码模拟它。实际上,我无法想象任何理由都会在第二个代码片段中一对一替换第一个代码片段。 DeadMG是正确的,std::unique_ptr::release只应用于如果你真的知道你在做什么以及在哪个上下文中搞乱了非托管动态对象。

+0

_“所以在实践中,两个代码片段之间通常没有任何行为差异。”这不是真的,一个移动删除器,一个不移动。如果删除者是有状态的,这非常重要。 –

+2

你知道,'std :: unique_ptr'的移动赋值运算符被指定为“通过调用'reset(u.release())'''将所有权从'u'转移到'* this', ”。该标准严重依赖于不会抛出的析构函数。 – Xeo

+0

@JonathanWakely哈,对!没有太多关于定制删除器的想法。 –

相关问题