Professional C__ - Marc Gregoire [90]
Here is a first definition of the copy constructor:
Spreadsheet::Spreadsheet(const Spreadsheet& src)
{
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new SpreadsheetCell* [mWidth];
for (int i = 0; i < mWidth; i++) {
mCells[i] = new SpreadsheetCell[mHeight];
}
for (int i = 0; i < mWidth; i++) {
for (int j = 0; j < mHeight; j++) {
mCells[i][j] = src.mCells[i][j];
}
}
}
Code snippet from Spreadsheet\SpreadsheetNoCopyFrom.cpp
Note that the copy constructor copies all data members, including mWidth and mHeight, not just the pointer data members. The rest of the code in the copy constructor provides a deep copy of the mCells dynamically allocated two-dimensional array. There is no need to delete the existing mCells because this is a copy constructor and therefore there is no existing mCells yet in this object.
The Spreadsheet Assignment Operator
Here is the definition for the Spreadsheet class with an assignment operator:
class Spreadsheet
{
public:
// Code omitted for brevity
Spreadsheet& operator=(const Spreadsheet& rhs);
// Code omitted for brevity
};
Code snippet from Spreadsheet\Spreadsheet.h
Here is the implementation of the assignment operator for the Spreadsheet class, with explanations interspersed. Note that when an object is assigned to, it already has been initialized. Thus, you must free any dynamically allocated memory before allocating new memory. You can think of an assignment operator as a combination of a destructor and a copy constructor. You are essentially “reincarnating” the object with new life (or data) when you assign to it.
The first lines of code in any assignment operator checks for self-assignment.
Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs)
{
// Check for self-assignment.
if (this == &rhs) {
return *this;
}
Code snippet from Spreadsheet\SpreadsheetNoCopyFrom.cpp
This self-assignment check is required, not only for efficiency, but also for correctness. If the preceding self-assignment test was removed, the code will most likely crash on self-assignment, because the second step in the code deletes mCells for the left-hand side and afterwards copies mCells from the right-hand side to the left-hand side. In the case of self-assignment, both sides are the same, so during copying you would access dangling pointers.
Because this is an assignment operator, the object being assigned to already has mCells initialized. You need to free these cells up.
// Free the old memory.
for (int i = 0; i < mWidth; i++) {
delete [] mCells[i];
}
delete [] mCells;
mCells = nullptr;
Code snippet from Spreadsheet\SpreadsheetNoCopyFrom.cpp
This chunk of code is identical to the destructor. You must free all the memory before reallocating it, or you will create a memory leak. The next step is to copy the memory.
// Copy the new memory.
mWidth = rhs.mWidth;
mHeight = rhs.mHeight;
mCells = new SpreadsheetCell* [mWidth];
for (int i = 0; i < mWidth; i++) {
mCells[i] = new SpreadsheetCell[mHeight];
}
for (int i = 0; i < mWidth; i++) {
for (int j = 0; j < mHeight; j++) {
mCells[i][j] = rhs.mCells[i][j];
}
}
return *this;
}
Code snippet from Spreadsheet\SpreadsheetNoCopyFrom.cpp
Note that this code looks remarkably like the code in the copy constructor. The following section will explain how you can avoid this code duplication.
The assignment operator completes the “big 3” routines for managing dynamically allocated memory in an object: the destructor, the copy constructor, and the assignment operator. Whenever you find yourself writing one of those methods you should write all of them.
Whenever a class dynamically allocates memory, write a destructor, copy constructor, and assignment operator.
C++11 includes a new concept called move semantics, which requires a move constructor and move assignment operator. These can be used to increase performance in certain situations and are discussed in detail in Chapter 9.
Common Helper Routines for Copy Constructor and Assignment Operator
The copy constructor and the assignment operator are