2012-02-26 72 views
29

在实现移动构造函数和移动赋值操作符,一个经常写这样的代码:为什么移动一个指针变量不会将它设置为null?

p = other.p; 
other.p = 0; 

隐含定义移动操作将与代码实现这样的:

p = std::move(other.p); 

这将是错误的,因为移动指针变量而不是将其设置为空。这是为什么?有没有我们希望移动操作离开原始指针变量的任何情况?

注:通过“移动”,我做仅仅意味着子表达式std::move(other.p),我指的是整个表达式p = std::move(other.p)。那么,为什么没有特殊的语言规则说:“如果赋值的右边是一个指针xvalue,它在赋值发生后被设置为空。”?

+5

为什么要这样?你应该对一个'移动'对象做的唯一事情就是忽略它。如果不再使用指针,指向不属于该类的内存的指针不应该成为问题,对吧? – hvd 2012-02-26 11:06:14

+5

“你不支付你不使用的东西”? – 2012-02-26 11:09:15

+7

@hvd:析构函数,如果它说'删除p' :) – fredoverflow 2012-02-26 11:10:40

回答

27

移动后设置一个原始指针为null则表示该指针代表所有权。但是,很多指针用于表示关系。此外,长期以来,建议所有权关系的表示方式与使用原始指针不同。例如,您所指的所有权关系由std::unique_ptr<T>表示。如果您希望隐式生成移动操作来处理您的所有权,您只需使用实际表示(并实现)所需所有权行为的成员。

而且,所产生的移动操作的行为是什么用的复制操作完成一致的:他们也没有做出任何所有权的假设,并不如做如果指针被复制,则为深层复制。如果你希望发生这种情况,你还需要创建一个合适的类来编码相关的语义。

+3

我只记得看过一篇文章说“移动不应该比复制更昂贵”或类似的东西。将原始指针设置为null会破坏该规则。你知道我正在谈论哪篇论文吗,或者我的大脑是如何制作的? – fredoverflow 2012-02-26 14:23:09

+1

+1。很好的解释。比我的好! – Nawaz 2012-02-26 14:55:36

4

我认为答案是:自己实现这样的行为是非常微不足道的,因此标准没有必要对编译器本身施加任何规则。 C++语言是巨大的,在使用之前并不是所有的东西都可以想象。以C++的模板为例。它并非首先被设计用于今天使用的方式(即它是元编程功能)。所以我认为,标准只是给了自由,并没有对std::move(other.p)做出任何具体规定,遵循其中一项设计原则:“你不支付你不使用的东西”

虽然,std::unique_ptr是可移动的,但不可复制。所以,如果你想指针的语义是可移动的,能够复制两个,那么这里是一个简单的实现:

template<typename T> 
struct movable_ptr 
{ 
    T *pointer; 
    movable_ptr(T *ptr=0) : pointer(ptr) {} 
    movable_ptr<T>& operator=(T *ptr) { pointer = ptr; return *this; } 
    movable_ptr(movable_ptr<T> && other) 
    { 
     pointer = other.pointer; 
     other.pointer = 0; 
    } 
    movable_ptr<T>& operator=(movable_ptr<T> && other) 
    { 
     pointer = other.pointer; 
     other.pointer = 0; 
     return *this; 
    } 
    T* operator->() const { return pointer; } 
    T& operator*() const { return *pointer; } 

    movable_ptr(movable_ptr<T> const & other) = default; 
    movable_ptr<T> & operator=(movable_ptr<T> const & other) = default; 
}; 

现在,你可以写的类,而不用自己写的举动语义:

struct T 
{ 
    movable_ptr<A> aptr; 
    movable_ptr<B> bptr; 
    //... 

    //and now you could simply say 
    T(T&&) = default; 
    T& operator=(T&&) = default; 
}; 

注您仍然必须编写复制语义和析构函数,因为movable_ptr而不是智能指针。

+0

但隐式定义的移动操作符不会调用'move_Ptr' ... – fredoverflow 2012-02-26 11:07:08

+3

@Fred - 如果您使用的是足够智能的指针而不是存储原始指针,它会。 *可以解决问题吗? – 2012-02-26 11:15:30

+0

@BoPersson:我认为这应该是一个答案? – Nawaz 2012-02-26 11:17:45

0

例如,如果您有指向共享对象的指针。请记住,移动对象后必须保持内部一致的状态,因此将不能为空的指针设置为空值是不正确的。

即:

struct foo 
{ 
    bar* shared_factory; // This can be the same for several 'foo's 
         // and must never null. 
}; 

编辑

下面是标准的报价约MoveConstructibe

T u = rv; 
... 
rv’s state is unspecified [ Note:rv must still meet the requirements 
of the library component that is using it. The operations listed in 
those requirements must work as specified whether rv has been moved 
from or not. 
+0

但是将指针变量设置为null只会发生在xvalues上。之后客户端将无法检查空指针。 – fredoverflow 2012-02-26 11:06:04

+0

如果你的析构函数以某种方式使用指针,并在UB为空时调用UB,该怎么办?另外,我不能用标准报价来支持我的说法,但据我所知,在移动对象必须保持有效之后(因为您可以继续正常使用它),而不仅仅是可破坏的。 – doublep 2012-02-26 11:09:54

+0

@FredOverflow:在标准中找到相关的引用。 – doublep 2012-02-26 11:13:59

5

搬家呈现移动,从对象“无效”。它确实不是自动将其设置为安全的“空”状态。按照C++长期坚持的原则:“不付出不使用的东西”,如果你想要的话,那就是你的工作。

相关问题