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

时间:2014-03-27 10:41:55

标签: 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;
}

输出

myvector contains: 1 2 3 4 5

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

myvector contains: 4 5 6 7 8

4 个答案:

答案 0 :(得分:12)

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

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

Example& operator= (Example eSource)
{
  swap(eSource);
  return *this;
}

注意操作符如何通过值获取其参数。这意味着将调用copy-constructor来构造参数,然后您可以只与该副本交换,有效地分配给{{1 }}

另请注意,*this期望通过引用返回;重载运算符时,始终遵循预期的约定。更重要的是,标准实际上需要 operator=CopyAssignable类型的赋值运算符来返回非const引用(C ++ 11 MoveAssignable和{ {1}});所以要正确使用带有标准库的类,必须遵守。

当然,它要求您在班级中实施[moveassignable]功能:

[copyassignable]

该函数不应引发异常(感谢@JamesKanze提及此问题),不要破坏swap()的异常安全性。

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

答案 1 :(得分:3)

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

该类的编译器生成的赋值运算符等效于:

Example &operator= (const Example &eSource)
{
    pValue = eSource.pValue;
    return *this;
}

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

[Alf会说返回void,大多数C ++程序员会说返回一个引用。无论您返回什么,重要行为都是pValueeSource.pValue值的分配。因为复制赋值操作符的作用是:从源复制到目标。]

答案 2 :(得分:2)

可能是operator=最常见的正确实施 将使用复制构造函数;你不想写同样的东西 代码两次。它会做类似的事情:

Example& Example::operator=( Example const& other )
{
    Example tmp( other );
    swap( tmp );
    return *this;
}

这里的关键是拥有一个swap成员函数来交换 内部代表,同时保证不投掷。

仅使用复制构造函数创建临时文件不是 足够。一个正确编写的赋值运算符将永远 返回引用,并返回*this

答案 3 :(得分:2)

首先,operator=()应该返回一个引用:

Example& operator=(const Example& eSource)
{
    pValue = eSource.pValue;
    return *this;
}

请注意,您的版本会返回tmp的副本,因此实际上它会执行两个副本。

其次,在你的课堂上,甚至不需要定义自定义赋值运算符或复制构造函数。编译器生成的那些应该没问题。

第三,你可能对复制和交换习语很感兴趣:What is the copy-and-swap idiom?