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

时间:2021-05-05 05:32:41

In an already existing class of a project I am working on I encountered some strange piece of code: The assignment operator calls the copy constructor.

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

I added some code and now the assignment operator seems to cause trouble. It is working fine though if I just use the assignment operator generated by the compiler instead. So I found a solution, but I'm still curious to find out the reason why this isn't working.

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

Since the original code is thousands of lines I created a simpler example for you to look at.

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

#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;
}

The output is

输出是

myvector contains: 1 2 3 4 5

but it should be (an in fact is, if I just use the compiler-generated assignment operator)

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

myvector contains: 4 5 6 7 8

4 个解决方案

#1


3  

The assignment operator you found is not correct. All it does is make a copy of eSource, but it's supposed to modify the object on which it is called.

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

The compiler-generated assignment operator for the class is equivalent to:

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

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

For this class there's no point implementing operator=, since the compiler-generated version basically cannot be improved on. But if you do implement it, that's the behaviour you want even if you write it differently.

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

[Alf will say return void, most C++ programmers will say return a reference. Regardless of what you return, the vital behaviour is an assignment to pValue of the value from eSource.pValue. Because that's what the copy assignment operator does: copy from the source to the destination.]

[Alf会说返回无效,大多数C ++程序员会说返回一个引用。无论你返回什么,重要的行为是从eSource.pValue赋值给pValue。因为这就是复制赋值操作符的作用:从源复制到目标。

#2


12  

Your operator= does not do what everyone (including the standard library) thinks it should be doing. It doesn't modify *this at all - it just creates a new copy and returns it.

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

It's normal to re-use the copy constructor in the copy assignment operator using the copy-and-swap idiom:

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

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

Notice how the operator takes its parameter by value. This means the copy-constructor will be called to construct the parameter, and you can then just swap with that copy, effectively assigning to *this.

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

Also note that it's expected from operator= to return by reference; when overloading operators, always follow the expected conventions. Even more, the standard actually requires the assignment operator of a CopyAssignable or MoveAssignable type to return a non-const reference (C++11 [moveassignable] and [copyassignable]); so to correctly use the class with the standard library, it has to comply.

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

Of course, it requires you to implement a swap() function in your class:

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

void swap(Example &other)
{
  using std::swap;
  swap(pValue, other.pValue);
}

The function should not raise exceptions (thanks @JamesKanze for mentioning this), no to compromise the exception safety of the operator=.

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

Also note that you should use the compiler-generated default constructors and assignment operators whenever you can; they can never get out of sync with the class's contents. In your case, there's no reason to provide the custom ones (but I assume the class is a simplified version for posting here).

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

#3


2  

First of all, operator=() should return a reference:

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

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

Mind that your version returns a copy of tmp so in fact it performs two copies.

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

Second of all, in your class there's no need to even define custom assignment operator or copy constructor. The ones generated by compiler should be fine.

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

And third of all, you might be interested in copy-and-swap idiom: What is the copy-and-swap idiom?

第三,你可能对复制和交换习语很感兴趣:什么是复制和交换习惯用法?

#4


2  

Probably the most frequent correct implementation of operator= will use the copy constructor; you don’t want to write the same code twice. It will do something like:

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

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

The key here is having a swap member function which swaps the internal representation, while guaranteeing not to throw.

这里的关键是有一个交换成员函数,它交换内部表示,同时保证不抛出。

Just creating a temporary using the copy constructor is not enough. And a correctly written assignment operator will always return a reference, and will return *this.

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

#1


3  

The assignment operator you found is not correct. All it does is make a copy of eSource, but it's supposed to modify the object on which it is called.

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

The compiler-generated assignment operator for the class is equivalent to:

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

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

For this class there's no point implementing operator=, since the compiler-generated version basically cannot be improved on. But if you do implement it, that's the behaviour you want even if you write it differently.

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

[Alf will say return void, most C++ programmers will say return a reference. Regardless of what you return, the vital behaviour is an assignment to pValue of the value from eSource.pValue. Because that's what the copy assignment operator does: copy from the source to the destination.]

[Alf会说返回无效,大多数C ++程序员会说返回一个引用。无论你返回什么,重要的行为是从eSource.pValue赋值给pValue。因为这就是复制赋值操作符的作用:从源复制到目标。

#2


12  

Your operator= does not do what everyone (including the standard library) thinks it should be doing. It doesn't modify *this at all - it just creates a new copy and returns it.

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

It's normal to re-use the copy constructor in the copy assignment operator using the copy-and-swap idiom:

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

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

Notice how the operator takes its parameter by value. This means the copy-constructor will be called to construct the parameter, and you can then just swap with that copy, effectively assigning to *this.

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

Also note that it's expected from operator= to return by reference; when overloading operators, always follow the expected conventions. Even more, the standard actually requires the assignment operator of a CopyAssignable or MoveAssignable type to return a non-const reference (C++11 [moveassignable] and [copyassignable]); so to correctly use the class with the standard library, it has to comply.

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

Of course, it requires you to implement a swap() function in your class:

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

void swap(Example &other)
{
  using std::swap;
  swap(pValue, other.pValue);
}

The function should not raise exceptions (thanks @JamesKanze for mentioning this), no to compromise the exception safety of the operator=.

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

Also note that you should use the compiler-generated default constructors and assignment operators whenever you can; they can never get out of sync with the class's contents. In your case, there's no reason to provide the custom ones (but I assume the class is a simplified version for posting here).

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

#3


2  

First of all, operator=() should return a reference:

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

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

Mind that your version returns a copy of tmp so in fact it performs two copies.

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

Second of all, in your class there's no need to even define custom assignment operator or copy constructor. The ones generated by compiler should be fine.

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

And third of all, you might be interested in copy-and-swap idiom: What is the copy-and-swap idiom?

第三,你可能对复制和交换习语很感兴趣:什么是复制和交换习惯用法?

#4


2  

Probably the most frequent correct implementation of operator= will use the copy constructor; you don’t want to write the same code twice. It will do something like:

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

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

The key here is having a swap member function which swaps the internal representation, while guaranteeing not to throw.

这里的关键是有一个交换成员函数,它交换内部表示,同时保证不抛出。

Just creating a temporary using the copy constructor is not enough. And a correctly written assignment operator will always return a reference, and will return *this.

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