在我正在处理的项目的现有类中,我遇到了一些奇怪的代码:赋值运算符调用了复制构造函数。
我添加了一些代码,现在赋值运算符似乎会造成麻烦。 如果我只使用编译器生成的赋值运算符,它工作正常。所以我找到了一个解决方案,但我仍然很想知道为什么这不起作用。
由于原始代码是数千行,因此我创建了一个更简单的示例供您查看。
#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
答案 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 ++程序员会说返回一个引用。无论您返回什么,重要行为都是pValue
对eSource.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?