这个部分的概念貌似简单,实则极为核心,是最基础的东西。因为构造函数,析构函数,拷贝构造函数,拷贝赋值函数很多时候都是默默调用的,比如,返回对象,堆上新建数组,等等,我们必须保证这几个基本的函数能够正常工作,才谈的上让类发挥作用,否则,只会陷入无尽的沉思
条款05:
显然,C++默认会有以下一些函数(都是public 和 inline的):
1、默认构造函数:所有的非内置类型成员不初始化,所有类类型成员用其默认构造函数初始化
2、析构函数:默认是非虚的
3、拷贝构造函数:Empty(const Empty & rhs) {...} 这是单纯将来源对象的每一个non-static成员变量拷贝到目标对象
4、拷贝赋值函数:与拷贝构造函数基本一致,只是类中的引用和常量不能赋值(不然就违反了这两个类型的意义了)。如果这个类要调用基类的拷贝构造函数,但是那个东西就private得,也编译器也不会生成copy assignment
条款06:怎样让一个类不能拷贝
把copy ctor 和 copy assignment 弄成private的,这样就可以了,一调用,编译的时候都错
上面的缺点:类的member function 和 友元 不受这个限制,还是有风险
解决方案1:只声明,不定义,那么,如果再member func 和 友元中用了上面两个函数,编译OK, link error
解决方案1的缺点:能早发现就早发现,最好是在编译的时候及早发现,早发现早治理
解决方案2:制作专门的类用于防止拷贝(就是个接口,但是C++中没有接口的概念)
class Uncopyable{
protected :
Uncopyable() {} // 子类还是可以构造和析构
~Uncopyable(){} // 析构函数不用虚了
private:
Uncopyable(const Unsopyable&) ; //但是组织copy,即使是子类
Uncopyable& operator=(const Uncopyable &) ;
};
运用:class HomeForSale : private Uncopyable{} ; // 注意这个地方不用public 继承了
条款07:多态基类 果基类的目的是为了多态,那么析构函数要虚
由来:当derived class 对象经由一个base class 指针被删除,而base class得析构函数不是虚的,其结果未定义,通常是base被销毁了,derived 的非base部分没有被销毁。
我们用virtual的目的,是为了子类实现个性化,因此,虚的目的是确有其用的,不能瞎虚,那样会影响移植性和代码性能
至少一个类里面有一个虚函数,我们才把这个类的析构函数弄成虚的。
书上一句话:"果你曾经企图继承一个标准容器或者任何其他带有non-virtual析构函数的class,拒绝诱惑"
如果你想让一个类成为抽象类,但是手头有没有纯虚函数,那么用析构函数做纯虚函数是个好主意
如果一个类的作用只是为了做基类,不是为了多态,那么就不需要virtual 析构函数,例如上面的 Uncopyable
条款08:析构函数千万表抛异常
这个条款我觉得写得不好,应当参考《more effective C++》item 11 我做个小小的总结。
1、两种情况下析构函数会被调用:一个对象的声明周期自然地结束;或者是前面抛了个异常,由于异常处理机制,导致对象被删除。
我们在析构函数中无法判断是那种情况激活了析构函数,如果析构再抛一个,那么控制权交到了函数外,C++将调用terminate函数。
2、我们说,析构不能抛出异常,主要是因为,我们捕捉不到,那样析构抛出的异常肆意乱跑,不受控制,导致不明确行为或者程序终止。
3、针对上述的问题,我们有的解决方法:析构函数吞下任何异常,catch(...)
4、因此,如果要对析构函数中的某个异常做出反应,我们应该在类中提供另外的接口,而不是在析构函数中。
条款09:构造函数和析构函数中不要调用虚函数
1、因为在base class构造期间,virtual 函数不是virtual 函数(间接调用也不行)
2、你要想在构造函数中实现虚的效果,只能依靠derived class做信息上传(base 的信息由derived 提供),书上的那个例子挺恶心的,我觉得,这个条例有用的地方在于:有些设计模式,例如模板模式,就有可能出现上述的情况,但是,我觉得,可以弄个单独的函数出来,但是,不要把这个放在构造或则会析构中。
条款10:令operator= 返回一个reference to * this
该条款适用所有的标准赋值形式。为什么呢?因为这样可以实现连锁赋值
x=y=x=15
条款11:在operator= 中处理“自我赋值”
1、没有哪个SB会直接自己给自己赋值,总是发生在无声无息中。为了代码健壮性,应当有所考虑。
有以下几种解决方案:
方案1:加上“证同测试”
Widget& Widget::operator=(const Widget& rhs)
{
if(this == &rhs) return * this // 证同测试
delete pb ;
pb = new Bitmap(*rhs.pb) ;
return *this ;
}
这个版本存在异常方面的问题,加入new 抛出异常,pb将指向被删除的地址。
方案2:如果异常安全,往往自动获得“自我赋值安全”的回报,所以,很多人将焦点放在异常安全
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb ;
pb = new Bitmap(*rhs.pb) ;
delete pOrig ;
return *this ;
}
方案3:copy and swap(后面细讲,听复杂,我不是特别懂)
class Widget{
void swap(Widget& rhs) ; // 前提是有一个函数,实现*this和rhs得数据交换
}
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs) ;
swap(temp) ;
return *this ;
}
条款12:复制对象别忘了每一样
copying函数一般有两个:copy 构造函数 和 copy assignment
如果我们自己去实现copy而不用编译器默认的,我们就要认真复制对象的每一项,包括基类的东西
我一开始以为要讲:深度复制,其实这个条款不是说这个的
记住:copying函数应该确保复制“对象内所有成员变量” 及 “所有base class 成分”
不要尝试以某个copying函数实现另一个copying函数,应该将共同的机能放进第三个函数,由两个copying函数去调用