自我分配有用吗?

时间:2019-05-26 18:27:46

标签: c++ assignment-operator

众所周知,在实现赋值运算符时,至少在类具有非POD成员的情况下,必须防止自我赋值。通常是(或等效于):

Foo& operator=(const Foo& other)
{
  if (&other == this)
     return *this;
  ... // Do copy
}

不自动插入自我分配保护的原因是什么?自我分配会带来一些琐碎且实用的事情吗?

Foo& operator=(const Foo& other)
{
  if (&other == this)
  {
    // Do something non-trivial
  }
  else
  {
    // Do copy
  }
  return *this;
}

现在总结答案和讨论

看起来像非平凡的自我分配永远不会真正有用。建议的唯一选择是在其中放置assert,以便检测某些逻辑错误。但是,有相当合法的自我指派案例,例如a = std::min(a, b),因此即使此选项也是非常可疑的。

但是,平凡的自我分配有两种可能的实现方式:

  1. 如果&other == this不执行任何操作。始终可以工作,尽管由于额外的分支可能会对性能产生负面影响。但是在用户定义的赋值运算符中,几乎必须始终明确地进行测试。
  2. 将每个成员复制到自己。这是默认情况下完成的操作。如果成员也使用默认的赋值运算符,则它可能会更快,因为它不需要额外的分支。

我仍然不明白为什么C ++标准不能在用户定义的赋值运算符&other != this中保证这一点。如果不希望分支,请使用默认运算符。如果您要重新定义操作员,则仍然需要进行一些测试...

3 个答案:

答案 0 :(得分:18)

仅当被跳过的代码应用于自身时很危险时,才需要自分配保护。考虑一下您有一个用户提供的赋值运算符的情况,因为每个单独的对象都有某种您不想复制的标识符。好吧,您可以在自我分配的情况下“复制”其他值。因此,插入一个不可见的自指派测试只是添加一个无意义且可能代价高昂的条件分支。

因此,自我分配并非有用。这是关于自我分配,并不总是需要保护。

此外,C ++通常不喜欢在未明确要求的情况下将类似的代码添加到代码中。通常是根据整个功能而不是功能的一部分完成的。当您将要销毁的对象放到堆栈上时,甚至要求在块末尾进行析构函数调用。

答案 1 :(得分:18)

有可能发生的算法。

  1. 您知道lhs和rhs可能是相同的,但是执行分配比检查要简单得多。例如,考虑a = std::min(a,b);-比if (a > b) a = b;更简单,也许更易于理解-现在考虑类似事物的更复杂示例。

  2. 您不知道lhs和rhs是否相同,因为它们可能是从其他地方传入的。

这些可能发生的算法并不罕见。

答案 2 :(得分:-1)

我应该承认我从未听说过这样的常识。对于非POD对象,一种更严格的方法是通过禁用复制构造函数和赋值运算符来禁止复制它们。这样您就完全没有问题了。

如果您仍然需要复制该类,但是有些数据不安全地复制到其自身上,则只能覆盖该数据的赋值,并且当将其用作字段时,它将被自动使用。上层课程的作业实现。

对于您的问题,如果您只需要跳过任何事情,则已经存在某种方式的自动“自我分配保护”。如果您未明确定义赋值操作,而让编译器使用自动操作,则在内联自赋值后可能会变成无操作。

例如,以下代码:

class A {
    int a;
    double b;
};

A& foo(A& input)
{
    return (input = input);
}

编译为(gcc 4.9,-O2):

_Z3fooR1A:
    .cfi_startproc
    movq    %rdi, %rax
    ret
    .cfi_endproc

不复制任何内容。