"检查自我分配"有什么问题?这是什么意思?

Jim*_*hen 28 c++ exception-handling assignment-operator exception-safety

在Herb Sutter的书Exceptional C++(1999)中,他在第10项的解决方案中有词:

"异常不安全"和"糟糕的设计"齐头并进.如果一段代码不是异常安全的,那通常是可以的并且可以简单地修复.但是,如果一段代码由于其底层设计而不能被设置为异常安全,那几乎总是这是其设计不佳的信号.

示例1:具有两种不同职责的职能很难使例外安全.

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

"检查自我分配"一词是什么意思?

[查询]

Dave和AndreyT向我们展示了"检查自我分配"的含义.非常好.但问题还没有结束.为什么"检查自我分配"会伤害"异常安全"(根据Hurb Sutter的说法)?如果调用者尝试进行自我分配,那么"检查"就像没有发生任何分配一样.真的疼吗?

[备忘1]在Herb的书后面的第38项对象身份中,他解释了自我分配.

AnT*_*AnT 36

在这种情况下,更重要的问题是"以这种方式编写,必须检查自我分配"的含义.

这意味着设计良好的赋值运算符不需要检查自我赋值.将对象分配给自身应该正常工作(即具有"无所事事"的最终效果),而不执行显式检查自我分配.

例如,如果我想实现一个简单的数组类

class array {
  ...
  int *data;
  size_t n;
};
Run Code Online (Sandbox Code Playgroud)

并提出了赋值运算符的以下实现

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

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

  return *this;
}
Run Code Online (Sandbox Code Playgroud)

该实施将被视为"坏",因为它在自我分配的情况下显然会失败.

为了"修复"它,可以添加一个明确的自我分配检查

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;
}
Run Code Online (Sandbox Code Playgroud)

或遵循"无检查"的方法

array &array::operator =(const array &rhs) 
{
  size_t new_n = rhs.n;
  int *new_data = new int[new_n];
  std::copy_n(rhs.data, new_n, new_data);

  delete[] data;

  n = new_n;
  data = new_data;

  return *this;
}
Run Code Online (Sandbox Code Playgroud)

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

这并不意味着您应该避免明确检查自我分配.从性能的角度来看,这样的检查是有意义的:执行一系列操作只是为了最终"无所事事".但是在设计良好的赋值操作符中,从正确的角度来看,这些检查不是必需的.

  • “从性能的角度来看,“ _这种检查确实有意义”。”集会上,您的程序中经常发生自我分配吗? (2认同)
  • @Aidiakapi - 你不是说 if (this == &rhs) return *this; (2认同)

cam*_*ino 9

来自 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;
}
Run Code Online (Sandbox Code Playgroud)

这显然是安全的,而且显然是有效的。但是,如果我们每百万次作业做一次自分配呢?这大约是一百万个冗余测试(但由于答案基本上总是相同的,计算机的分支预测器基本上每次都会猜对)。考虑:

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

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

  • 如果某些东西不是动态分配的怎么办?这是同一件事的不同方面。 (2认同)