简体   繁体   English

当有复制构造函数时,是否总是需要赋值运算符?

[英]Is assignment operator always necessary when there is a copy constructor?

I am learning the constructor and destructor, and have learned the rule of three . 我正在学习构造函数和析构函数,并且已经学会了三个规则

I am now playing a small example from tutorialspoint . 我现在正在播放tutorialspoint中的一个小例子。 I notice the example doesn't have an assignment operator, but the code somehow works well. 我注意到该示例没有赋值运算符,但代码以某种方式运行良好。 eg, as to Line a(b) , when I change the content in a, eg, *(a.ptr), *(b.ptr) is not changed. 例如,对于Line a(b) ,当我改变a中的内容时,例如*(a.ptr),*(b.ptr)不改变。

I also write one assignment operator (commented), the code also works. 我还写了一个赋值运算符(注释),代码也可以。

Now I am confused. 现在我很困惑。 It seems on some occasions, only copy constructor is enough. 在某些情况下,似乎只有复制构造函数就足够了。 Could anyone comment on this to help me know better the mechanism of memory allocation involved in calling copy constructor? 任何人都可以对此发表评论,以帮助我更好地了解调用复制构造函数所涉及的内存分配机制吗?

Thanks! 谢谢!

#include <iostream>

using namespace std;

class Line
{
   public:
      int getLength( void );
      Line( int len );             // simple constructor
      Line( const Line &obj);  // copy constructor
      ~Line();                     // destructor
      void doubleLength(void);
     Line &operator=(const Line &);

   private:
      int *ptr;
};

// Member functions definitions including constructor
Line::Line(int len)
{
    cout << "Normal constructor allocating ptr" << endl;
    // allocate memory for the pointer;
    ptr = new int; //simply allocates memory for one integer, and returns a pointer to it.
    *ptr = len;
}

Line::Line(const Line &obj)
{
    cout << "Copy constructor allocating ptr." << endl;
    ptr = new int;
   *ptr = *obj.ptr; // copy the value
}

//  // copy assignment operator, added by me
//  Line& Line::operator=(const Line& that)
//  {
//      if (this != &that)
//      {
//          delete ptr;
//          // This is a dangerous point in the flow of execution!
//          // We have temporarily invalidated the class invariants,
//          // and the next statement might throw an exception,
//          // leaving the object in an invalid state :(
//          this->ptr = new int;
//          this->ptr = that.ptr;

//      }
//      return *this;
//  }


Line::~Line(void)
{
    cout <<  "Freeing memory " << ptr << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}

void Line::doubleLength(void)
{
    *ptr = *ptr * 2;
}

void display(Line obj)
{
   cout << "Length of line : " << obj.getLength() <<endl;
}

// Main function for the program
int main( )
{
   Line line1(10);

//     Line line2 = line1; // This also calls copy constructor
   Line line2(line1);   // performed by copy assignment operator    
   line2.doubleLength();
   display(line1);
   display(line2);


   return 0;
}

I get the output: 我得到输出:

Normal constructor allocating ptr
Copy constructor allocating ptr.
Copy constructor allocating ptr.
Length of line : 10
Freeing memory 0x836c028
Copy constructor allocating ptr.
Length of line : 20
Freeing memory 0x836c028
Freeing memory 0x836c018
Freeing memory 0x836c008
Line line2 = line1; //assignment operator but copy constructor will be called implicitly
Line line3(line1); //copy constructor will be called explicitly

When a new object is created from an existing object then copy constructor is called. 从现有对象创建新对象时,将调用复制构造函数。 Here line2 and line3 are newly created from existing line1 , so copy constructor is called even though you use = . 这里line2line3是从现有的line1新创建的,所以即使你使用=也会调用copy构造函数。

Line line2; //default constructor is called
line2 = line1; //assignment operator is called

Here the first line declares and initializes it with default constructor and the second one is assignment. 这里第一行用默认构造函数声明并初始化它,第二行是赋值。 So assignment operator is called here. 所以在这里调用赋值运算符。 ie When an object is assigned to an already initialized object then assignment operator is called. 即,当将对象分配给已初始化的对象时,将调用赋值运算符。

The assignment operator is not required, but may be desirable. 赋值运算符不是必需的,但可能是合乎需要的。

If you have a class Foo , 如果你有一个Foo课,

Foo a;     // calls normal constructor
Foo b(a);  // calls copy constructor explicitely
Foo c = b; // calls copy constructor (initialization)
b = a;     // calls assignment operator

When an object is copied as the result of a function call (value parameter), the copy constructor is called. 当作为函数调用(值参数)的结果复制对象时,将调用复制构造函数。

The following cases may result in a call to a copy constructor: 以下情况可能导致调用复制构造函数:

  • When an object is returned by value 按值返回对象时
  • When an object is passed (to a function) by value as an argument 当一个对象通过值作为参数传递(到函数)时
  • When an object is thrown 当一个物体被抛出时
  • When an object is caught 捕获对象时
  • When an object is placed in a brace-enclosed initializer list 将对象放在括号括起的初始化列表中时

These cases are collectively called copy-initialization and are equivalent to Obj x = a; 这些情况统称为复制初始化,相当于Obj x = a;

It is however, not guaranteed that a copy constructor will be called in these cases, because the C++ Standard allows the compiler to optimize the copy away in certain cases, one example being the return value optimization(RVO) 但是,不保证在这些情况下将调用复制构造函数,因为C ++标准允许编译器在某些情况下优化副本,一个例子是返回值优化(RVO)

The term return value optimization refers to a special clause in the C++ standard that goes like : an implementation may omit a copy operation resulting from a return statement, even if the copy constructor has side effects 术语返回值优化是指C ++标准中的特殊子句,它类似于: 实现可以省略由return语句产生的复制操作,即使复制构造函数具有副作用

The RVO is particularly notable for being allowed to change the observable behaviour of the resulting program by the C++ standard RVO特别值得注意的是,允许通过C ++标准更改结果程序的可观察行为

An object can be assigned value using one of the two techniques: 可以使用以下两种技术之一为对象分配值:

  • Explicit assignment in an expression 表达式中的显式赋值
  • Initialization 初始化

Explicit assignment in an expression (invoke simple copy, not copy constructor!) 表达式中的显式赋值 (调用简单副本,而不是复制构造函数!)

Object a;
Object b;
a = b;//translates as Object::operator=(const Object&)thus a.operator=(b)is called 

Initialization (invoke copy constructor) 初始化 (调用复制构造函数)

An object can be initialized by any one of the following ways. 可以通过以下任何一种方式初始化对象。

a. 一种。 Through declaration 通过声明

Object b = a; // translates as Object::Object(const Object&) 

b. Through function arguments 通过函数参数

type function(Object a);

c. C。 Through function return value 通过函数返回值

Object a = function();

Is assignment operator always necessary when there is a copy constructor? 当有复制构造函数时,是否总是需要赋值运算符?

The copy constructor is used only for initializations, and does not apply to assignments where the assignment operator is used instead. 复制构造函数仅用于初始化,不适用于使用赋值运算符的赋值。

Your code may seems to work but there are scenarios in which it will probably crash. 您的代码似乎可以正常工作,但有些情况可能会崩溃。 Look at your dtor, it frees the memory pointed by ptr . 看看你的dtor,它释放了ptr指向的内存。 Alas if you assign an object into another one, the default assignment operator will only copy the value of the pointer (shallow copy) and you then have two objects each with a pointer pointing to the same memory chunk. 如果将对象分配给另一个对象,则默认赋值运算符将仅复制指针的值(浅拷贝),然后您有两个对象,每个对象都指向同一个内存块。 This is the problem. 这就是问题。 It is not forbidden to do so, but you need to take care when the memory can be freed, etc. As you used a copy ctor to obtain a deep copy, you also need to get an assignment operator to make the same deep copy. 不禁止这样做,但是在释放内存时需要注意等等。当您使用复制ctor获取深层复制时,您还需要使用赋值运算符来制作相同的深层复制。 This is why there is a Rule of Three (uniform behavior for your object for all "copy/assign" scenarios)... 这就是为什么存在三个规则(对于所有“复制/分配”场景的对象的统一行为)...

 // Line line2 = line1; // This also calls copy constructor 

Yes, because the mere presence of the = character alone does not indicate that an assignment operator is called. 是的,因为仅存在=字符并不表示调用赋值运算符。 As cppreference.com explains: 正如cppreference.com所说:

The equals sign, = , in copy-initialization of a named variable is not related to the assignment operator. 在命名变量的复制初始化中,等号= ,与赋值运算符无关。 Assignment operator overloads have no effect on copy-initialization. 赋值运算符重载对复制初始化没有影响。

Then there's the following line in your code: 然后在代码中有以下行:

  Line line2(line1); // performed by copy assignment operator 

This comment is wrong. 这个评论是错误的。 Why should the copy assignment operator be called here? 为什么要在这里调用复制赋值运算符?

There is one important relationship between the copy constructor and the copy assignment operator, though, and that is that one should usually be implemented in terms of the other, using the copy-and-swap idiom . 但是,复制构造函数和复制赋值运算符之间存在一个重要的关系,那就是通常应该使用复制和交换习惯用另一个来实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM