《Effective C++》学习笔记(条款12:复制对象时勿忘其每一个成分)

《Effective C++》学习笔记(条款12:复制对象时勿忘其每一个成分),第1张

《Effective C++》学习笔记(条款12:复制对象时勿忘其每一个成分)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

C++中有两个函数负责对象拷贝,分别是拷贝构造函数和拷贝赋值运算符,我们可以统称为拷贝函数。若我们不声明自己的拷贝函数,则编译器会给你提供,若是声明了自己定义的拷贝函数,必须把所有成员变量拷贝。

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

Customer::Customer(const Customer& rhs):name(rhs.name){   //使用初始化列表
	logCall("Customer copy constructor");
}

Customer& Customer::operator=(const Customer& rhs){
    logCall("Customer copy assignment operator");
    name = rhs.name;    //拷贝数据
    return *this;       //返回*this
}

上述代码中自己声明的拷贝函数都很不错,直到另一个成员变量的出现

class Date {...};
class Customer{
public:
    ...
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
private:
    string name;
    Date lastTransaction;	//新出现的成员变量
};

这时候上述的拷贝函数只是局部拷贝,它们的确复制了顾客的 name,但没有赋值新出现的成员变量 lastTransaction。大多数编译器不会因此而发出错误信息,即你的拷贝函数不完整,它不会告诉你。总的来说,你每添加一个成员变量,你就必须修改已有的拷贝函数(也需要修改 class 的所有搞糟函数以及任何非标准形式的 operator=)。

一旦发生继承呢?假设 PriorityCustomer 类继承了 Customer 类,如下述代码:

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

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
  	:priority(rhs.prority){           //使用初始化列表来构造该类的数据成员
  	logCall("PriorityCustomer copy constructor");  
}

PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
  	logCall("PriorityCustomer copy assignment operator");
	priority = rhs.priority;          //拷贝该类的数据成员
	return *this;
}

PriorityCustomer 的拷贝函数确实完整了复制 PriorityCustomer 中的专属成分,但它的基类 Customer 中的专属成分却未被复制。PriorityCustomer 的拷贝构造函数并没有指定实参传给其基类的构造函数(即它再它的初始化列表中没有提到 Customer ),因此 PriorityCustomer 中其基类的专属成分会被无参构造函数(必须有一个无参构造函数,不然无法通过编译,因为你定义了拷贝函数,所以编译器不会给你提供默认构造函数)初始化。

解决方法:

//拷贝构造函数:在初始化列表上调用其基类的拷贝函数
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
  	:Customer(rhs),			//调用基类的拷贝构造函数
	priority(rhs.prority){
  	logCall("PriorityCustomer copy constructor");  
}

//拷贝赋值运算符:对基类成分进行赋值 *** 作
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
  	logCall("PriorityCustomer copy assignment operator");
	Customer::operator=(rhs);	//对基类成分进行赋值 *** 作
    priority = rhs.priority;
	return *this;
}

所以,当自己编写一个拷贝函数时,请确保:

  • 复制所有本地成员变量
  • 调用所有基类内的适当的拷贝函数

注意:在拷贝赋值运算符函数中调用拷贝构造函数是不合理的,反之,在拷贝构造函数中调用拷贝赋值运算符同样是无意义的。

Note:

  • 拷贝函数应确保复制“对象内的所有成员变量”及“所有基类成分”
  • 不要尝试以某个拷贝函数实现另一个拷贝函数。应将共同机能放进第三方函数中,并由两个拷贝函数共同调用

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5115488.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-17
下一篇 2022-11-17

发表评论

登录后才能评论

评论列表(0条)

保存