Copy Constructor | Copy Assignment Operator |
It is called when a new object is created from an existing object, as a copy of the existing object | This operator is called when an already initialized object is assigned a new value from another existing object. |
It creates a separate memory block for the new object. | It does not create a separate memory block or new memory space. |
It is an overloaded constructor. | It is a bitwise operator. |
C++ compiler implicitly provides a copy constructor, if no copy constructor is defined in the class. | A bitwise copy gets created, if the Assignment operator is not overloaded. |
Syntax: className(const className &obj) { // body } | Syntax: className obj1, obj2; obj2 = obj1; |
Classes come with copy assignment operator (CAO) the built-in behaviour just assigns all fields. Thus, you may need to write your own (e.g. built-in would shallow copy node).
Why is the above dangerous?
When writing operator =
, always consider self-assignment. The copy operator above is very wrong. We need to make a deep copy version:
(Perhaps ask for more clarity on the below)
The correct version above is a lot safer basically.
This link is a basically perfect explanation of the Copy Swap Idiom: https://www.youtube.com/watch?v=7LxepUEcXA4
So, we get this in the end:
Copy and Swap Idiom
The copy and swap idiom is commonly used to implement an assignment operator that provides the strong exception guarantee for a resource managing class. Suppose we start the below class:
So, the above is the definition of the class DynArray
. In the above, we need we’re missing our assignment operator. We know that a assignment operator will return a reference to itself:
-
Now, the above will work for most of the time. It doesn’t leak any memory. However, it does not maintain the state of an object if an exception/error is thrown.
-
For example, say we go into the constructor and we first delete the data associated with the object through
delete [] m_data;
, rest the size usingm_size = other.m_size;
, and then, imagine that when we are “new”-ing an array, an error occurs and an exception is thrown. Well, while we aren’t leaking any memory, the state of our object has changed. We want to avoid this and provide a strong exception guarantee.- Strong exception guarantee — If the function throws an exception, the state of the program is rolled back to the state just before the function call.
-
We accomplish this through the copy and swap idiom.
First, we change the order of the above code. One way we can do this is to move the operations as follows:
Since the operations we are doing are solely on temporary objects, we no longer need to test for self assignment.
Now notice that the first three lines are literally doing what the copy constructor does. So, we can just instead create a temporary object to be a copy of the passed in parameter. Then, we need to swap the data of the copy and the object we want (this
). So, we can use the std::swap
. So, after these changes, our final code looks like:
Move Semantics
The final part of this lecture is looking at move semantics.
Recall:
- an l-value is anything with an address
Now consider:
If the definition of n2 involves the copy constructor (it does), when what is other? Other itself is an l-value reference but what object does it refer to? The compiler creates a temporary object to be the return value of plusOne, and the other is the reference to this temporary, and the copy constructor deep copies this temporary.
So, an l-value and r-value are different types of values in c++. For example, look at the code below:
So in the above code snipped, firstname
, lastname
, and fullname
are l-values. That is, these variables have assignable memory addresses. On the other hand, the values “allan”
, “yin”
, and firstname + lastname
, are r-values, temporary pieces of data that do not have accessible address. Say we have the function:
Now, note, if we were to modify the printName function as follows:
Both print functions would be fine. So, if we write a function that expects a const reference
, then passing it l-values or r-values will work.
However, a better and more clear way of doing so is to write a function that expects r-value references. We do so with &&
This is handy since now, we can just overload printName
, and pass it either l-values or r-values, does not matter.
Suppose we have the class below: