为什么移动指针变量不会将其设置为空?

49

在实现移动构造函数和移动赋值运算符时,通常会编写以下代码:

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

隐式定义的移动操作将使用以下代码实现:

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

这是错误的,因为移动指针变量并不会将其设置为 null。为什么呢?是否有任何情况下我们希望移动操作后原始指针变量保持不变?

注意:在此,“移动”并不仅指子表达式 std::move(other.p),而是整个表达式 p = std::move(other.p)。那么,为什么没有特殊的语言规则说:“如果赋值的右侧是一个指向 xvalue 的指针,则在赋值完成后它将被设置为 null。”?


5
为什么需要这样做呢?对于一个“移动”的对象,唯一的事情就是忽略它。如果你不再使用指向该类未拥有的内存的指针,那么指针指向未拥有的内存不应该成为问题,对吧? - user743382
5
“你不会为你没有使用的东西付费。” - Bo Persson
9
如果说 delete p,析构函数肯定不会忽略它。 - fredoverflow
5
自动生成的析构函数永远不会说delete p,那么你想表达什么意思? - user743382
6
std::swap 对两个指针进行交换是一个很好的例子,说明在不希望 std::move 自动将指针置为 NULL 时应该如何操作。至于整数的“空”状态是什么?确实,编译器优化可以使 std::swap 的情况再次达到理想状态,但我认为这些情况表明我们最好不要去改变它。 - Johannes Schaub - litb
显示剩余13条评论
5个回答

38

在移动指针后将原始指针设置为null意味着该指针代表所有权。然而,很多指针用于表示关系。此外,长期以来推荐使用不同于使用原始指针来表示所有权关系的方式。例如,您所提到的所有权关系是由std::unique_ptr<T>表示的。如果您想要隐式生成的移动操作处理您的所有权,您需要做的就是使用实际表示(并实现)所需所有权行为的成员。

此外,生成的移动操作的行为与对复制操作的处理一致:它们也不做任何所有权假设,并且如果复制指针,则不执行深度复制等操作。如果您希望发生这种情况,您还需要创建一个适合的类来编码相关语义。


4
我记得读过一篇论文,说过“移动的成本不应该比复制更高”,或者类似的内容。将原始指针设置为null会违反这个原则。你知道我说的是哪篇论文吗?还是我的大脑编造了这个信息? - fredoverflow
我对第一句话“将原始指针设置为null后移动,意味着该指针代表所有权”并不完全赞同。为什么会这样?我不同意。在现代代码库中,堆所有权由智能指针处理。原始指针仅相当于引用,除了开发人员可以选择引用“无”。在旧的代码库中,您不知道指针是用于拥有堆还是仅引用某些内容。因此,将指针设置为null并不意味着拥有它。 - Don Pedro

8

移动操作会使被移动对象“无效”。它不会自动将其设置为安全的“空”状态。根据C++长期以来的原则,“不为你不使用的东西付费”,如果需要,那就是你的工作。


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 不是智能指针。

3
如果您使用足够智能的指针而不是储存原始指针,那么它会有所帮助。可能这个是问题所在? - Bo Persson
1
@BoPersson 这个和“你不用支付你没有使用的东西”的说法会是一个很好的回答。 - fredoverflow
1
移动操作简单地定义为调用成员的移动操作,请参见12.8 §15。 - fredoverflow
@FredOverflow:如果是这种情况,那这是编译器的bug吗:http://ideone.com/tMqUo ?还是我漏掉了什么东西?(我期望输出为“moved (using move-assignment)”)。 - Nawaz
12.8 §9 表示您的预期输出应该是正确的。ideone 使用 gcc-4.5.1,我最好的猜测是这个版本还没有正确地实现规则。 - fredoverflow
显示剩余3条评论

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

I.e.:

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

编辑

这是一段关于标准中 MoveConstructible 的引用:

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.

如果你的析构函数使用指针,并且在指针为空时会引发未定义行为,那该怎么办呢?另外,我无法通过标准中的引用来支持我的说法,但据我记得,移动对象后它必须保持有效(即你可以继续正常使用它),而不仅仅是可析构的。 - user319799
@doublep:MoveConstructible要求仅适用于标准容器,没有其他。此外,定义对象的“内部一致状态”。 - Xeo
@Xeo:即使如此,如果C++ stdlib包含(将移动出的指针置空)的功能,这将违反其对其他部分的要求,这将非常奇怪。 - user319799
@Xeo:此外,您必须遵循“MoveConstructible”要求,以确保您的对象与需要元素为“MoveConstructible”的标准容器良好配合。 - user319799
@Johannes Schaub:默认移动构造函数将移动所有字段,而不关注它是否“正确”。此外,在模板元编程中,将所有类型视为相同并假定如果一个类型具有移动构造函数,则它是可移动的(否则移动将隐式回退到复制)。 - user319799
显示剩余3条评论

0

我认为这里的区别在于一方是完整的对象,另一方是POD。

  • 对于对象,实现者要么指定移动构造和移动赋值应该做什么,要么编译器生成默认值。默认值是调用所有成员的移动构造函数/赋值运算符。
  • 对于POD(指针是POD),C++继承自C,在没有明确编码时不会对其进行任何处理。这与在构造函数中处理类中的POD成员的行为相同。如果您没有将它们明确放入初始化列表中,那么它们将保持未初始化状态,并可能成为潜在错误的源头。据我所知,即使是编译器生成的构造函数也是如此!这就是为什么我养成了通常将所有成员初始化的习惯。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接