此篇文章的核心内容其实也很简单,就是题目所讲的那样,在写拷贝构造函数和赋值运算符重载函数的时候别忘了拷贝你类中的任何一个成份,尤其是派生类更需要注意这点。
下面我简单总结一下文章的内容吧。
1.为什么会提这个条款呢?因为设计良好的面向对象系统的就是将对象的内部封装起来,只留两个函数负责对象的拷贝(复制),这两个函数就是拷贝构造函数和赋值运算符重载函数,所以有必要提一下关于这两个函数应该注意的一些事情。
.当你写一个如下代码的时候一切都正常
class Stu
{
private:
std::string tmp;
public:
Stu(const Stu& s) :tmp(s.tmp){}//拷贝构造函数
Stu& operator=(const Stu& s)
{
tmp = s.tmp;
return *this;
}
};
拷贝构造函数和赋值运算符重载函数都很顺利,继续看如下代码
class Stu
{
private:
int data;
std::string tmp;
public:
Stu(const Stu& s) :tmp(s.tmp){}//拷贝构造函数
Stu& operator=(const Stu& s)
{
tmp = s.tmp;
return *this;
}
}
当你加入新的成员变量的时候而忘记去修改这两个函数,这个时候函数执行的是局部拷贝,只是复制了tmp但是并没有复制新的数据data,注意此时编译器并不会报任何错误和警告
或许这就是编译器对你不使用他创造的函数而去使用自己的函数的一种报复手段吧,哈哈。
所以在此需要注意的是如果你给一个 类添加了新的成员,一定记得修改他的相关函数,不然编译器是不会提醒你
2.继承体系中的派生类中的拷贝构造函数和赋值运算符重载问题
class Stu
{
private:
int data;
std::string tmp;
public:
Stu(){};
Stu(const Stu& s) :tmp(s.tmp){}//拷贝构造函数
Stu& operator=(const Stu& s)
{
tmp = s.tmp;
return *this;
}
};
class Dervice :public Stu
{
private:
int _x;
public:
Dervice(const Dervice& s) :_x(s._x){}
Dervice& operator=(const Dervice& s)
{
_x = s._x;
return *this;
}
};
①对于拷贝构造函数来说:
派生类继承基类的部分并未被复制,因为派生类的拷贝构造函数没有指定实参传给基类的构造函数(也就是说在它的成员初始列值没有提到Stu),所以继承来的基类的成员只能被Stu的默认构造函数(这个默认构造函数必须有否则无法通过编译的)初始化,默认构造函数将对继承来的基类成员函数进行默认的初始化动作。所以这个拷贝构造函数拷贝出来的派生类对象的基类成员并没有如愿异常的被复制。
改正:在派生类的拷贝构造函数中调用基类拷贝构造函数
Dervice(const Dervice& s) :_x(s._x),Stu(s){}
②对于赋值运算符重载
他和拷贝构造函数差不多,只不过是他的继承的基类成员的值是保持原来的值不变,而不是被默认构造函数初始化。
改正:在派生类的赋值运算符重载函数调用基类的拷贝构造函数
Dervice& operator=(const Dervice& s)
{
Stu::operator=(s);
_x = s._x;
return *this;
}
为什么不直接在派生类的函数函数中给基类赋值呢,因为基类成员往往是私有的,所以自己还是调用私有的方式最好了。
现在你对这个条款应该很清除了吧,当你写一个copying函数,请记得:①复制所有的local成员变量②调用所有基类内的适当的copy函数。
最后一点要注意的问题就是
1.别再拷贝构造函数中调用赋值运算符重载函数
解释:因为构造函数是用来初始化新对象,而赋值运算符重载函数是对于已经初始化的对象身上(已经存在的对象身上),对一个尚没有构造好的对象进行赋值,就像在一个尚未初始化的对象身上做“只对已经初始化对象才有意义的事”一样,没意义,别去常识
2.别再赋值运算符重载函数中调用拷贝构造函数
解释:因为这就像试图构造一个已经存在对象,是很荒谬的一件事情,根本没有相关的语法。可能有一些看起来如你所愿的语法,其实不是,可能有些语法背后真正做了它,但是这会造成你原本有的对象败坏,所以单纯的记住别乱调用就好了!
如果发现拷贝构造函数和赋值运算符重载函数的代码相似,避免代码重复的办法是让这些代码存在另外的函数中,让拷贝构造函数和赋值运算符重载函数去调用它就好了。并且这个函数常常是private而且命名为init.