条款05:了解C++默默编写并调用了那些函数
class empty{}; //创建一个没有任何方法的空类 class empty{ //编译器实际为你声明的类 public: empty(){...} //默认构造函数 empty(const empty& rhs){...} //拷贝构造函数 ~empty(){...} //析构函数 empty& operator=(const empty& rhs){...} //拷贝赋值操作符 };
1.构造函数,析构函数,拷贝构造函数,拷贝赋值操作符是类最基本要实现的功能,如果你自己不写,编译器也会为你声明,且这些函数都是public inline函数。
2.编译器创建的构造函数,析构函数负责调用基类的,和非类专属成员的构造函数析构函数。
3.拷贝构造函数与拷贝赋值操作符单纯将每一个成员进行拷贝。
4.类中含不可更改的成员,编译器不会帮你写拷贝构造函数与拷贝赋值操作符,因为结果是不明确的。
条款06:若不想使用编译器自动生成的函数,就该明确拒绝
方法一.将对应成员函数声明为private并不予实现
1.原因:如果我们不想有基础方法,如果不写的话,编译器就会帮忙生成。
2.处理:在private中写只有声明,没有定义的方法。
3.原理:
①当编译器发现类中有声明,就不会再自己生成。
②因为这些方法在private中,又无法被直接调用。
③如果公共方法与友元强行使用,而这些方法并没有定义,编译器就会报出错误。
方法二.使用Uncopyable的基类
1.Uncopyable类:将拷贝的两种方法填入private中的类。
2.原理:其派生类要调用拷贝时,会去调用基类的方法,而基类却拒绝让其使用拷贝方法,报出错误。
条款07:为多态基类声明virtual析构函数
一.方法
1.原因:使用指向派生类的指针,但是以基类的方式去释放,导致派生类的析构函数未被调用,派生类的成分没有被销毁。
2.处理:给基类创建一个virtual析构函数。
3.原理:销毁时,可以调用合适的析构函数,从而避免局部销毁。
二.注意
1.如果不用于派生,不要给基类创建virtual方法。
原因:virtual方法需要类给予额外的vptr(virtual table pointer)用于存储virtual函数表,导致内存小号变大。
原理:每一个vptr指向一个vtbl(virtual table),编译器选择合适的函数,重写函数,实现多态。
2.不要将带有non-virtual析构函数的类当作基类(string,map,list...)
原因:仍会出现局部销毁的风险
3. 析构函数调用过程
从最深层派生类的析构函数开始调用,直到基类,基类的析构函数需要有定义(即使是纯虚函数)。
条款08:别让异常逃离析构函数
1.析构函数绝对不要吐出异常。要在析构函数中捕捉异常,防止其传播造成不明确的行为。
2.如果客户需要对异常作出反应,应该自己定义异常捕捉函数,而不是在析构函数中完成。
条款09:绝不在构造和析构过程中调用virtual函数
1.原因:当你的派生类调用构造函数时,先调用基类构造函数,而基类构造函数调用的virtual函数,不会重写为派生类中的,也就是说,这时的virtual函数直接当成普通函数来用。
2.处理:派生的方法将参数传递到基类的方法中,参数应该是静态变量,避免出现未定义的情况。
条款10:令operator=返回一个reference to *this
1.为了能够实现连锁赋值,一般来说都要求如此,还包括了+=、-=、*=、/=等等。
条款11:在operator=中处理“自我赋值”
1.原因:在拷贝赋值过程中,想要进行资源管理,但是由于目的成员与源成员指向同一个,把源成员释放掉导致无法赋值成功。(注意:基类的指针或引用可以指向派生类)
class Widget{ ... private: Bitmap* pb; }; Widget& Widget::operator=(const Widget& rhs) { delete pb; //所释放的恰好与rhs相同,则pb在接下来会指向已经被删除的对象 pb = new Bitmap(*this.pb); return *this; }
2.处理:
①证同:如果是同一个,直接返回,不同则采取资源管理的措施。
Widget& Widget::operator=(const Widget& rhs) { if(this == &rhs) return *this; //证同 delete pb; pb = new Bitmap(*rhs.pb); return *this; }
②上述仍存在如果new Bitmap时,产生异常,此时pb也会指向已经被删除的对象,所以我们在赋值之前不要释放原指针。
Widget& Widget::operator=(const Widget& rhs) { Bitmap* pOrig = pb; pb = new Bitmap(*rhs.pb); delete pOrig; //动态申请的仍然需要自己释放 return *this; } Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); //局部变量会自己释放 swap(temp); //将*this与temp交换 return *this; }
条款12:复制对象时勿忘其每一个成分
1.由编译器自己生成的拷贝函数会把所有成员都进行拷贝
2.自己编写拷贝函数可能会漏掉,尤其是派生函数赋值时,千万不要忘记要在初始化列表中加上基类的构造函数。
3.拷贝构造函数与拷贝赋值符不能相互调用,如果嫌代码重复,可以重写一个private init()函数。
原文链接: https://www.cnblogs.com/sujian/p/12469296.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;
也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/334915
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!