C++ 是否存在自我分配有用的情况?

C++ 是否存在自我分配有用的情况?,c++,assignment-operator,C++,Assignment Operator,众所周知,在实现赋值运算符时,必须防止自赋值,至少在类具有非POD成员时是这样。通常是(或相当于): 未自动插入自分配保护的原因是什么?当自我分配做了一些非琐碎且实际有用的事情时,是否存在用例 Foo& operator=(const Foo& other) { if (&other == this) { // Do something non-trivial } else { // Do copy } return *this

众所周知,在实现赋值运算符时,必须防止自赋值,至少在类具有非POD成员时是这样。通常是(或相当于):

未自动插入自分配保护的原因是什么?当自我分配做了一些非琐碎且实际有用的事情时,是否存在用例

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

现在总结答案和讨论

看起来非平凡的自我分配永远不会真正有用。建议的唯一选项是在那里放置一个
断言
,以便检测一些逻辑错误。但是有一些非常合法的自分配情况,比如
a=std::min(a,b)
,所以即使是这个选项也是非常可疑的

但有两种可能的简单自分配实现:

  • 如果
    &other==此
    ,则不执行任何操作。始终工作,但由于额外的分支可能会对性能产生负面影响。但是,在用户定义的赋值运算符中,测试几乎总是显式进行的
  • 将每个成员复制到自身。这是默认情况下所做的。如果成员也使用默认的赋值运算符,则可能会更快,因为不需要额外的分支

  • 我仍然不明白为什么C++标准不能保证用户定义的赋值操作符<代码>等等!这是。如果不希望分支,请使用默认运算符。如果要重新定义运算符,无论如何都需要进行一些测试…

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

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


    <>此外,C++一般不喜欢在代码中添加类似代码,而不需要明确地请求。它通常是在整个函数中完成的,而不是函数的一部分。当您将要销毁的对象放在堆栈上时,甚至在块的末尾调用析构函数也是您需要的。

    有一些算法可以实现这一点

  • 您知道lhs和rhs可能是相同的,但执行赋值比检查简单。例如,考虑<代码> A= STD:::min(a,b);<代码>-如果(a>b)a=b,则比
    更简单,可能更容易理解现在考虑类似的事情更复杂的例子。

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


  • 这些可能发生这种情况的算法并不少见。

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

    如果您仍然需要复制该类,但有一些数据复制到自身是不安全的,则只能重写该数据的赋值,并且当该数据用作字段时,上层类的自动赋值实现将使用该数据

    至于你的问题,如果你只需要跳过任何事情,自动“自我分配保护”在某种程度上已经存在了。如果不明确定义赋值操作,并让编译器使用自动赋值,内联后,自赋值可能会变为不可操作

    例如,以下代码:

    class A {
        int a;
        double b;
    };
    
    A& foo(A& input)
    {
        return (input = input);
    }
    
    编译为(gcc 4.9,-O2):


    这是一个有趣的问题。我甚至会说,典型的自我分配是某种逻辑错误的指示器,使用断言或其他形式的严格检查是有意义的。我可以肯定地认为它是无害的,但没有用处。嗯,C++标准库已经足够疯狂地超载<代码> <代码和代码> <代码>,并进行有效的操作。也许他们认为有人可能想用
    =
    做类似疯狂的事情。条件分支可能会比“只做”慢“检查,可能什么也不做”,因此默认情况下不应该进行检查。我还可以想象,在这种情况下,您可能想知道它发生的频率,可能是出于性能原因。如果语言是“隐藏”自我分配,这将很难分析。你可能会发现这是一个有趣的阅读:问题是关于一个非琐碎的自我分配。我想说,可能处理的自我分配使情况(1)更加复杂,因为我们首先需要在比较
    a
    b
    时进行分支,然后可能在检查自分配时进行分支,而不仅仅是单个分支。OP询问的是自分配有用的情况,而不是自分配保护是否必要或何时应实施。此外,我也不同意“自我分配保护仅在被跳过的代码应用于自身时存在危险的类型中是必要的。”即使自我分配本身对捕获调用代码中的问题是完全无害的,这样的检查也是非常有帮助的。类似于
    foo[i]=foo[i]
    而不是
    foo[i]=foo[j]
    的打字错误或类似问题经常发生,这篇文章的第二句话是“没有自动插入自分配保护的原因是什么?”原则问题之所以存在,是因为假设如果自我分配的概念没有某种效用,这种自动保护就会存在。我的帖子反驳了这一假设,并且曾经反驳过这一假设
    class A {
        int a;
        double b;
    };
    
    A& foo(A& input)
    {
        return (input = input);
    }
    
    _Z3fooR1A:
        .cfi_startproc
        movq    %rdi, %rax
        ret
        .cfi_endproc