“有什么问题吗?”;“检查自我分配”;这意味着什么? 《例外C++》(1999)中,他在第10条的解决方案中有:

“有什么问题吗?”;“检查自我分配”;这意味着什么? 《例外C++》(1999)中,他在第10条的解决方案中有:,c++,exception-handling,assignment-operator,exception-safety,C++,Exception Handling,Assignment Operator,Exception Safety,“异常不安全”和“糟糕的设计”齐头并进。如果一段代码不是异常安全的,那么这通常是可以解决的。但是,如果一段代码由于其底层设计而无法实现异常安全,那么这几乎总是其糟糕设计的信号 示例1:具有两种不同职责的函数很难保证异常安全 示例2:以必须检查自赋值的方式编写的复制赋值运算符可能也不是强异常安全的 他所说的“检查自我分配”是什么意思 [查询] Dave和AndreyT向我们展示了“检查自我分配”的确切含义。那很好。但问题还没有结束。为什么“检查自我分配”会损害“异常安全”(根据Hurb Sutte

“异常不安全”和“糟糕的设计”齐头并进。如果一段代码不是异常安全的,那么这通常是可以解决的。但是,如果一段代码由于其底层设计而无法实现异常安全,那么这几乎总是其糟糕设计的信号

示例1:具有两种不同职责的函数很难保证异常安全

示例2:以必须检查自赋值的方式编写的复制赋值运算符可能也不是强异常安全的

他所说的“检查自我分配”是什么意思

[查询]

Dave和AndreyT向我们展示了“检查自我分配”的确切含义。那很好。但问题还没有结束。为什么“检查自我分配”会损害“异常安全”(根据Hurb Sutter的说法)?如果调用者试图进行自我分配,那么“检查”的作用就好像从未发生过分配一样。真的痛吗

[备忘录1]在赫伯书后面的第38项对象标识中,他解释了自我赋值。

MyClass&MyClass::operator=(const MyClass&other)//复制赋值运算符
MyClass& MyClass::operator=(const MyClass& other)  // copy assignment operator
{
    if(this != &other) // <-- self assignment check
    {
        // copy some stuff
    }

    return *this;
}
{
如果(this!=&other)/在这种情况下,更重要的问题是“写的方式必须检查自我分配”是什么意思

这意味着一个设计良好的赋值操作符不需要检查自赋值。将对象赋值给自身应该可以正常工作(即具有“不做任何事情”的最终效果),而不需要执行自赋值的显式检查

MyClass& MyClass::operator=(const MyClass& other)  // copy assignment operator
{
    if(this != &other) // <-- self assignment check
    {
        // copy some stuff
    }

    return *this;
}
例如,如果我想实现一个简单的数组类

class array {
  ...
  int *data;
  size_t n;
};
并提出了以下赋值运算符的实现

array &array::operator =(const array &rhs) 
{
  delete[] data;

  n = rhs.n;
  data = new int[n];
  std::copy_n(rhs.data, n, data);

  return *this;
}
这种实现将被认为是“糟糕的”,因为在自我分配的情况下,它显然失败了

为了“修复”它,可以添加一个显式的自分配检查

array &array::operator =(const array &rhs) 
{
  if (&rhs != this) 
  {
    delete[] data;

    n = rhs.n;
    data = new int[n];
    std::copy_n(rhs.data, n, data);
  }

  return *this;
}
或者采用“不检查”的方法

从某种意义上讲,后一种方法更好,因为它在自分配情况下可以正确工作,而无需对其进行明确检查。(从异常安全的角度来看,此实现还远远不够完善,这里是为了说明处理自分配的“已检查”和“无检查”方法之间的区别)。通过众所周知的复制和交换习惯用法,可以更优雅地编写后面的无检查实现


这并不意味着您应该避免对自我分配进行显式检查。从性能的角度来看,这种检查是有意义的:执行长时间的操作序列以“不做任何事情”告终是没有意义的最后。但在设计良好的赋值运算符中,从正确性的角度来看,不需要进行此类检查。

检查自赋值的一般原因是,在复制到新的数据之前,您销毁了自己的数据。这种赋值运算符结构也不是非常例外安全的

作为补充,我们确定自分配对性能没有任何好处,因为每次都必须运行比较,但自分配非常罕见,如果确实发生,这是程序中的逻辑错误(真的)这意味着在程序的整个过程中,这只是浪费循环。

< >从C++核心指南

Foo& Foo::operator=(const Foo& a)   // OK, but there is a cost
{
    if (this == &a) return *this;
    s = a.s;
    i = a.i;
    return *this;
}
这显然是安全且有效的。但是,如果我们每百万个任务做一个自我分配怎么办?这大约是一百万个冗余测试(但由于答案基本上总是相同的,计算机的分支预测器基本上每次都猜对了)。考虑:

Foo& Foo::operator=(const Foo& a)   // simpler, and probably much better
{
    s = a.s;
    i = a.i;
    return *this;
}

注意:上面的代码只适用于没有指针的类,对于指针指向动态内存的类。请参考Ant的答案。

@Mooing Duck不要对我的答案进行如此彻底的编辑。我甚至不同意你添加的内容-这是一个简单的解决方法,几乎总是次优的。有时可以,有时不可以。写一个你自己的答案nswer。我想如果答案中同时包含一个好方法和坏方法的样本,会更好。对不起,如果我越界了。你是对的,它是次优的,但它是相当标准的,因为它足够快,而且很难搞砸。如果你能提供一些正确的方法来完成任务,我会投赞成票。(不必是复制和交换,你可以不同意我的观点)@MooingDuck我认为几乎所有的时候,正确的方法都是不显式地提供复制作业。如果你设计好你的类,默认值就是你想要的。那应该在答案中。“以这样的方式,它必须”这里最重要的一个词是:必须“这是程序中的逻辑错误(实际上)。"事实并非如此。自分配是一个无意义的语句。无意义的语句从来都不是程序员故意编写的,因此总是错误。不,自分配应该没有效果。它不是无意义的。从定义上讲,没有效果是无意义的,因为程序在执行前后是相同的。自分配并不总是显而易见的。假设许多STL算法(分区、排序等)会对元素进行自我赋值是完全合理的。“从性能角度来看,这样的检查是有意义的”总而言之,自我分配经常发生在你的程序中?@curiousguy:取决于程序,取决于算法。如果它允许自我分配,那就这样吧。无分支代码总是比分支代码看起来更优雅,这意味着在这种情况下我会允许自我分配发生,而不是试图从外部捕捉和阻止它。好吧,我应该承认,理解“checkforself-assignment”语句需要一个强大的上下文。我认为当使用
if(this!=&rhs)返回*this;