简体   繁体   中英

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 . 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.

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 = .

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

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)

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

The RVO is particularly notable for being allowed to change the observable behaviour of the resulting program by the C++ standard

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. 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 . 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. 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:

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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