bugprone-unhandled-self-assignment

cert-oop54-cpp 作为此检查的别名重定向至此。对于 CERT 别名,WarnOnlyIfThisHasSuspiciousField 选项设置为 false

查找没有通过显式检查自赋值或使用复制和交换或复制和移动方法来保护代码免受自赋值的用户定义的复制赋值运算符。

默认情况下,此检查仅搜索具有任何指针或 C 数组字段的类,以避免误报。如果存在指针或 C 数组,如果复制赋值运算符没有小心编写,则自复制赋值可能会破坏对象。

另请参阅:OOP54-CPP。优雅地处理自复制赋值

复制赋值运算符必须防止自复制赋值破坏对象状态。一个典型的用例是当类有一个指针字段并且复制赋值运算符首先释放指向的对象,然后尝试将其赋值

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  T& operator=(const T &rhs) {
    delete p;
    p = new int(*rhs.p);
    return *this;
  }
};

有两种常见的 C++ 模式可以避免此问题。第一个是自赋值检查

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  T& operator=(const T &rhs) {
    if(this == &rhs)
      return *this;

    delete p;
    p = new int(*rhs.p);
    return *this;
  }
};

第二个是复制和交换方法,当我们创建一个临时副本(使用复制构造函数),然后将此临时对象与 this 交换

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  void swap(T &rhs) {
    using std::swap;
    swap(p, rhs.p);
  }

  T& operator=(const T &rhs) {
    T(rhs).swap(*this);
    return *this;
  }
};

还有一个不太常见的第三种模式。我们称它为复制和移动方法,当我们创建一个临时副本(使用复制构造函数),然后将此临时对象移动到 this 中(需要移动赋值运算符)

class T {
int* p;

public:
  T(const T &rhs) : p(rhs.p ? new int(*rhs.p) : nullptr) {}
  ~T() { delete p; }

  // ...

  T& operator=(const T &rhs) {
    T t = rhs;
    *this = std::move(t);
    return *this;
  }

  T& operator=(T &&rhs) {
    p = rhs.p;
    rhs.p = nullptr;
    return *this;
  }
};
WarnOnlyIfThisHasSuspiciousField

true 时,检查将仅在复制赋值运算符的容器类具有任何可疑字段(指针或 C 数组)时发出警告。此选项默认设置为 true