Article 12: do not forget every component when copying objects

A well-designed object-oriented system (OO systems) encapsulates the interior of an object, leaving only two functions responsible for copying (copying) the object. That is the copy constructor and copy assignment operator with appropriate names, which I call the copying function. Article 5 observes that the compiler will create copying functions for our classes when necessary, and explains the behavior of these "compiler generated versions": make a copy of all member variables of the copied object.

If you declare your own copying function, you tell the compiler that you don't like some behavior in the default implementation. The compiler doesn't tell you what's wrong with copying.

void logCall(const std::string& funcName);  // Create a log entry
class Customer {
public:
	...
	Customer(const Customer& rhs);
	Customer& operator=(const Customer& rhs);
private:
	std::string name;
};

Customer::Customer(const Customer& rhs)
	:name(rhs.name)                             // Copy rhs data
{
	logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
	logCall("Customer copy assignment operator");
	name = rhs.name;                                 // Copy rhs data
	return *this;                                    // See clause 10
}

Everything here looks good, but actually everything is good, until another member variable is added to the war:

class Date {...};
class Customer {
public:
	...
private:
	std::string name;
	Date lastTransaction;
}

At this time, the existing copying functions perform local copy: they do copy the customer's name, but they do not copy the newly added lastTransaction. Most compilers have no complaints about this - even at the highest warning level (see Article 53). This is the compiler's revenge on "you write the copying function yourself".

Specific examples:

#include<iostream>
using namespace std;

void logCall(const std::string& funcName)  // Create a log entry
{
	cout<<funcName<<endl;
}
class Customer {
public:
	Customer(string name, string address);
	Customer(const Customer& rhs);
	Customer& operator=(const Customer& rhs);
	void printCustomer();
private:
	std::string name;
	std::string address;
};

Customer::Customer(string name, string address)
	:name(name)
	,address(address)
{
	logCall("Customer constructor");
}

Customer::Customer(const Customer& rhs)
	:name(rhs.name)                             // Copy rhs data
{
	logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
	logCall("Customer copy assignment operator");
	name = rhs.name;                                 // Copy rhs data
	return *this;                                    // See clause 10
}

void Customer::printCustomer()
{
	cout<<"name:"<<name<<",address:"<<address<<endl;
}

int main(int argc, char* argv[])
{
	Customer cus("jack", "beijing");
	cus.printCustomer();
	Customer cus2(cus);
	cus2.printCustomer();
	return 0;
}

Output:

Customer constructor
name:jack,address:beijing
Customer copy constructor
name:jack,address:

Once inheritance occurs, it may cause a greater crisis on this subject. Consider:

class PriorityCustomer:public Customer {
public:
	...
	PriorityCustomer(const PriorityCustomer& rhs);
	PriorityCustomer& operator=(const PriorityCustomer& rhs);
	...
private:
	int priority;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
	:priority(rhs.priority)
{
	logCall("PriorityCustomer copy constructor");
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
	logCall("PriorityCustomer copy assignment operator");
	priority = rhs.priority;
	return *this;
}

The copy function of PriorityCustomer seems to copy everything in PriorityCustomer, but take a second look. Yes, they copy the member variables declared by the PriorityCustomer, but each PriorityCustomer also contains a copy of the Customer member variables it inherits, while those member variables are not copied. The copy constructor of PriorityCustomer does not specify an argument to its base class constructor (that is, it does not mention Customer in the member initialization list). Therefore, the Customer component of the PriorityCustomer object will be initialized by the Customer constructor without an argument (that is, the default constructor - there must be one or it cannot be compiled). Initialization. The default constructor performs the default initialization action for name and lastTransaction.

The above situation is only slightly different in the copy assignment operator of PriorityCustomer. It did not attempt to modify the member variables of its base class, so those member variables remain unchanged.

Whenever you take the responsibility of "writing copying function for derived class", you must carefully copy its base class component. Those components are often private (see Clause 22), so you can't access them directly. You should ask the copying function of derived class to call the corresponding base class function:

class PriorityCustomer:public Customer {
public:
	...
	PriorityCustomer(const PriorityCustomer& rhs);
	PriorityCustomer& operator=(const PriorityCustomer& rhs);
	...
private:
	int priority;
};

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
	:Customer(rhs),               // Call the copy constructor of base class
	priority(rhs.priority)
{
	logCall("PriorityCustomer copy constructor");
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
	logCall("PriorityCustomer copy assignment operator");
	Customer::operator=(rhs);      // Assign value to base class component
	priority = rhs.priority;
	return *this;
}

"Copy every ingredient" mentioned in the title of this article should be clear now. When you write a copying function, make sure that:

  1. Copy all local member variables;

  2. Call the appropriate copying function within all base classes.

please remember

  • The Copying function should ensure that all member variables within the object and all base class components are copied.

  • Do not try to implement another copying function with one copying function. The common function should be placed in the third function and called by both copying functions.

Added by rayfinkel2 on Sun, 27 Oct 2019 07:42:00 +0200