在赋值运算符中调用复制构造函数

Pet*_*ete 5 c++ copy-constructor assignment-operator

在我正在处理的项目的现有类中,我遇到了一些奇怪的代码:赋值运算符调用了复制构造函数.

我添加了一些代码,现在赋值运算符似乎会造成麻烦.如果我只使用编译器生成的赋值运算符,它工作正常.所以我找到了解决方案,但我仍然很想知道为什么这不起作用.

由于原始代码是数千行,因此我创建了一个更简单的示例供您查看.

#include <iostream>
#include <vector>

class Example {

private:
  int pValue;
public:
  Example(int iValue=0)
  {
    pValue = iValue;
  }

  Example(const Example &eSource)
  {
    pValue = eSource.pValue;
  }

  Example operator= (const Example &eSource)
  {
    Example tmp(eSource);
    return tmp;
  }

  int getValue()
  {
    return pValue;
  }

};

int main ()
{
  std::vector<Example> myvector;

  for (int i=1; i<=8; i++) myvector.push_back(Example(i));

  std::cout << "myvector contains:";
  for (unsigned i=0; i<myvector.size(); ++i)
    std::cout << ' ' << myvector[i].getValue();
  std::cout << '\n';

  myvector.erase (myvector.begin(),myvector.begin()+3);

  std::cout << "myvector contains:";
  for (unsigned i=0; i<myvector.size(); ++i)
    std::cout << ' ' << myvector[i].getValue();
  std::cout << '\n';

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出是

myvector contains: 1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

但它应该是(事实上,如果我只使用编译器生成的赋值运算符)

myvector contains: 4 5 6 7 8
Run Code Online (Sandbox Code Playgroud)

Rei*_*ica 13

operator=不会做每个人(包括标准库)认为它应该做的事情.它根本不会修改*this- 它只是创建一个新副本并返回它.

使用copy-and-swap惯用法在复制赋值运算符中重用复制构造函数是正常的:

Example& operator= (Example eSource)
{
  swap(eSource);
  return *this;
}
Run Code Online (Sandbox Code Playgroud)

注意操作符如何按获取其参数.这意味着将调用copy-constructor来构造参数,然后您可以只与该副本交换,有效地分配给*this.

另请注意,预计operator=将通过引用返回; 重载运算符时,始终遵循预期的约定.更重要的是,该标准实际上要求 a CopyAssignableMoveAssignable类型的赋值运算符返回非const引用(C++ 11 [moveassignable][copyassignable]); 所以要正确使用带有标准库的类,必须遵守.

当然,它要求您swap()在类中实现一个函数:

void swap(Example &other)
{
  using std::swap;
  swap(pValue, other.pValue);
}
Run Code Online (Sandbox Code Playgroud)

该函数不应该引发异常(感谢@JamesKanze提到这一点),不要破坏异常的安全性operator=.

另请注意,应尽可能使用编译器生成的默认构造函数和赋值运算符; 他们永远不会与班级的内容不同步.在您的情况下,没有理由提供自定义的(但我认为该类是在此处发布的简化版本).

  • @JamesKanze:编码约定是老式的 - 如果你真的需要副本,那么通过值传递可以提高异常安全性并为编译器提供更好的优化机会 (2认同)

Ste*_*sop 3

您找到的赋值运算符不正确。它所做的只是复制 的副本eSource,但它应该修改调用它的对象。

编译器为该类生成的赋值运算符相当于:

Example &operator= (const Example &eSource)
{
    pValue = eSource.pValue;
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

对于此类,实现 没有意义operator=,因为编译器生成的版本基本上无法改进。但是,如果您确实实现了它,那么即使您以不同的方式编写它,这也是您想要的行为。

[Alf 会说 return void,大多数 C++ 程序员会说返回一个引用。无论您返回什么,重要的行为都是对pValuefrom 的值进行赋值eSource.pValue。因为这就是复制赋值运算符的作用:从源复制到目标。]