这一篇博客记录我学习Effective C++ term12的学习笔记!
(若日后再次遇到与本term有关的不懂的问题,我都可以再翻越Effective C++ term12看自己记录的notes,再补充上我这一篇博客,那么就ojbk了)
Effective C++ term12 教会我:复制对象时勿忘其每一个成分(重要且常用的条款)
先给出结论:
①Copying函数应该确保复制“对象内的all的成员变量”and“all的base class的成分”
②不要尝试以某一个copying函数来实现另外一个copying函数,若想消除重复的代码,具体做法就是建立一个新的成员函数给copy构造 和copy assignment 函数来调用,而这样的函数往往是private的且常常被命名为init。这个策略可以安全地消除copy构造函数和copy assignment操作符之间的重复代码。
这两条结论必须要记住!
下面废话不多说,请看以下代码:
(对着注释去看代码,就可以知道本term12所讨论的问题的所在了!)
#include<iostream>
#include<string>
using std::cout;
using std::cin;
using std::endl;
//本.cpp,我们将学习Effective C++ 的条款12
void logCall(const std::string& funcName) //标记函数
{
}
class Date
{
//...
};
class Customer
{
public:
Customer(){}
//...
Customer(const Customer& rhs);//拷贝构造
Customer& operator=(const Customer& rhs);//拷贝赋值
//...
private:
std::string m_name;
//Date lastTransaction;
};
Customer::Customer(const Customer& rhs):m_name(rhs.m_name)
{
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
logCall("Customer copy assignment operator");
this->m_name = rhs.m_name;//复制rhs中的数据给this
return *this;//term10教的代码规范
}
class PriorityCustomer :public Customer
{
public:
//...
PriorityCustomer(){}
//非标准规范的复制对象的代码
PriorityCustomer(const PriorityCustomer& rhs):priority(rhs.priority)
{
//这样的代码看似好像很好,但其实由于PriorityCustomer继承了Customer这个类,
//因此PriorityCustomer中其实还包含了Customer类的成员变量的副本,而这个副本在这里并没有
//被复制
//因此base class中的成员m_name以及lastTransaction你都没去赋值,让
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
this->priority = rhs.priority;
return *this;
}
//任何时候,只要你承担起为子类撰写copying函数的重责大任时,必须要很小心的复制其base class的成分
//而这些成分往往是private的,so你无法直接访问它们,你应该让derived class 的copying函数调用相应的
//base class 的构造函数
//比如:(下面就是标准的复制对象时勿忘每个成分的优秀代码)
PriorityCustomer(const PriorityCustomer& rhs)
:Customer(rhs)//调用base class 的拷贝构造函数
,priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=(const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
Customer::operator=(rhs);//对base class的成分也进行赋值的动作
this->priority = rhs.priority;
return *this;
}
//那么现在呢,本term所说的“复制每一个成分”现在应该很clear了
//请你记住:当你编写一个copying函数时,请确保:
//①复制all的local成员变量
//②调用all的base classes内的适当的copying函数
//也只有这样,你才能在复制对象时把每一个成分都复制到位了!
//
//当然,上述的copying函数的代码都有近似相同的实现本体,若想精益求精,你大可以另外写一个函数,我们一般叫做init函数
//用于管理重复的代码,来避免代码的重复!
// 当然,令copy构造函数调用copy assignment函数并不是一个好的选择,反正你别这么干就行了!
//...
private:
int priority;
};
知道上述问题之后,我写了个小例子,来告诉自己,以后在写继承时,copying函数(包括copy构造函数以及copy assignment函数)应该如何处理对象的赋值问题了!就是要把对象all的成分都要复制一遍!
请看下面的规范代码:
#include<iostream>
using namespace std;
#include<string>
class Base
{
public:
Base(){}//默认构造
Base(int age, string name) :m_Age(age), m_Name(name)
{ cout << "this is base class 构造函数" << endl; };
Base(const Base& b)
{
this->m_Age = b.m_Age;
this->m_Name = b.m_Name;
cout << "this is base class copy 构造函数" << endl;
}
Base& operator=(const Base& b)
{
this->m_Age = b.m_Age;
this->m_Name = b.m_Name;
cout << "this is base class copy assignment 函数" << endl;
return *this;
}
virtual void showInfo() {}
int& getAge()
{
return this->m_Age;
}
string& getName()
{
return this->m_Name;
}
private:
int m_Age;
string m_Name;
};
class Son :public Base
{
public:
Son(){}//默认构造
Son(double sco, int age, string name):m_score(sco),Base(age,name)
{
cout << "this is Son class 构造函数" << endl;
};
Son(const Son& s):m_score(s.m_score), Base(s)//拷贝构造函数也是可以用初始化list来出丝滑的
{ //这里你一上来就用初始化list来给Son类的对象s中的Base class成分赋值,这就是良好的codes!
//if你不这样干的话当你使用 Son s2 = s1;类似这样拷贝赋值的操作的时候就没法把Base class中的成分赋值过来
//这样的话你再输出Son s2的all属性的话,其中s2的Base class 属性就会乱码!因为你没有给它们初始化!!!
cout << "this is Son class copy 构造函数" << endl;
}
Son& operator=(const Son& s)
{
this->m_score = s.m_score;
Base::operator=(s);
cout << "this is Son class copy assignment 函数" << endl;
return *this;
}
virtual void showInfo()
{
cout << "分数:" << this->m_score << "\t年龄:" << this->getAge() <<
"\t姓名:" << this->getName() << endl;
}
private:
double m_score;
};
void test()
{
Son s1(404.0, 32, "lzf");//当然,你需要按照你子类的构造函数的参数顺序来实例化
Son s2;
s2 = s1;
Son s3(s1);
cout << "s1的Info:" << endl;
s1.showInfo();
cout << "s2的Info:" << endl;
s2.showInfo();
cout << "s3的Info:" << endl;
s3.showInfo();
}
int main(void)
{
test();
system("pause");
return 0;
}
上述代码的运行结果(是很漂亮的,很规范的,很准确无误的):
如果以后你每一个继承的类都能写出如上述的代码,那么你离写出合格的C++代码就一定不远了!!!加油干吧,凡凡!
但,请你注意到这个成员函数!!!
Son(const Son& s):m_score(s.m_score), Base(s)
{
cout << "this is Son class 拷贝函数" << endl;
}
坑一:这里你一上来就用初始化list来给Son类的对象s中的Base class成分赋值,这就是良好的codes!
if你不这样干的话当你使用 Son s2(s1);类似这样拷贝的操作的时候就没法把Base class中的成分赋值过来,这样的话你再输出Son s2的all属性的话,其中s2的Base class 成员属性就会乱码!因为你没有给它们初始化!!!
比如把上面这个成员函数的代码改成如下:
Son(const Son& s):m_score(s.m_score)
{
cout << "this is Son class 拷贝函数" << endl;
}
再覆盖到原代码中的这个成员函数身上,那么运行结果则是:
上述所说的乱码逐一体现在运行结果上了,你仔细品即可!
坑二:当然,这里面还有一个坑需要你去注意!那就是:
用一个copying函数去实现另外一个copying函数,这是坑!
(当然,copy assignment函数不存在这个问题,因为你既然能赋值了那肯定是预先已经存在了的对象才能够给我这个对象做赋值操作!)
比如:Son s1,s2;
s2=s1;
这样ojbk的
这是很荒谬的!这就像是试图去构造一个已经存在了的对象
比如把上面这个成员函数的代码改成如下:
Son(const Son& s) :m_score(s.m_score)
{
Base::Base(s);//在一个copying函数中去调用另一个copying函数
cout << "this is Son class copy 拷贝函数" << endl;
}
运行结果:
这里虽然也调用了Base class 的copy 构造函数,但是这里是把Son的copy构造函数内去调用的Base::Base(s);中的s当做是一个已经存在了的对象,把它里面的Base class 成分赋值给自己的Base class 成分,这不就是自己赋值 给自己嘛,本来自己Son3就没初始化,还有用自己的Base class成分给自己赋值一遍,这显然不会如你所愿!
或者,你可以这样理解,因为这里Son的copy 构造函数中,传入的参数是const Son& s
有引用的类型,因此你不能再让s赋值为别人(也包括s赋值为自己),也即对于引用类型而言,因为为引用必须在定义的时候初始化,并且不能重新赋值,所以必须要写在初始化列表中,也即
调用Base的copy构造函数初始化Son类对象的Base class类成分(成员属性)时,必须要写在Son类的copy构造函数中的初始化列表中 !
综上,我相信对于本term12的学习已经足够清晰了!
这里再次给出总结:
①Copying函数应该确保复制“对象内的all的成员变量”and“all的base class的成分”
(具体做法上述已经说得很clear了!日后忘记的话再回看自己写的这一篇博客即可!)
②不要尝试以某一个copying函数来实现另外一个copying函数,若想消除重复的代码,具体做法就是建立一个新的成员函数给copy构造 和copy assignment 函数来调用,而这样的函数往往是private的且常常被命名为init。这个策略可以安全地消除copy构造函数和copy assignment操作符之间的重复代码。
参考:
Effective C++ 之条款12