c++太麻烦(Effective C++学习笔记(2))
条款5:了解C++默默编写并调用那些函数
如果类中没有声明构造函数 ,编译器会帮你生出一个default构造函数 、copy构造函数 、copy assignment操作符以及析构函数 。其中 ,default构造函数和析构函数会分别自动调用基类(父类)成员变量的构造函数和析构函数 ,而copy构造函数和copy assignment操作符只是简单地将每一个非static成员变量拷贝到目标对象(浅拷贝) 。 有两种情况下编译器拒绝自动为class生成operator=:
(1)类内含引用&成员变量和const成员变量 ,这两者一经初始化就不允许再赋值 ,但可自定义copy操作符支持赋值;
(2)如果某个基类将copy assignment操作符设为private ,以至于派生类无法访问 。条款6:如果不想使用编译器自动生成的函数 ,就应该明确拒绝
如果想要阻止编译器暗自创建copy构造函数、copy assignment操作符 ,同时阻止其他人调用它们 ,有以下两种实现方法:
(1)将这两个成员函数声明为private并且故意不实现它们,形参参数名也可以不写 ,因为根本不会实现它们:
(2)(将链接器错误转移至编译器)继承一个包含private声明的copy构造函数 、copy assignment操作符但不实现的基类 ,然后继承它 。因为编译器生成的copy会尝试调用基类对应的函数,由于private ,则会被拒绝 。
条款7:为多态基类声明virtual析构函数
当derived class对象经由一个base class(带着非virtual析构函数)指针删除时 ,其结果未定义—通常是derived class自己的成员没有被删除,造成对象“局部销毁 ” 。
(1)解决方法:给基类一个虚析构函数 (2)底层原理:本质上是多态的体现 。基类一个虚析构函数 ,其派生类的析构函数将继承虚函性质 ,也是一个虚函数 。当一个类有虚函数时 ,实例化对象中会有一个虚函数表指针vptr ,指向该对象的虚函数表 。虚函数表中存放了该对象的所有虚函数。虚析构函数也会放到虚函数表中 。当基类指针指向派生类对象时 ,虚函数表中的析构函数是派生类自己的析构函数 。因此 ,当delete此基类指针时 ,vptr在虚函数表中查询并执行的析构函数是派生类自定义的析构函数 ,而非基类的析构函数。与此同时 ,派生类析构函数被调用后,基类析构函数自动调用 。也就是说 ,派生类对象拥有的基类和自己的成员均被析构 。 带有多态性质基类应该声明一个虚析构函数。如果类带有任何虚析构函数 ,它就应该拥有一个虚析构函数 。 不要把标准库的类当作基类,可能它们不含任何虚函数 。因此有可能发生上述“局部销毁 ”的情况 。 另一种解决对象“局部销毁 ”做法是为抽象类添加纯虚析构函数 ,让其他类继承它 。需要为其声明 ,并且定义(因为派生类执行析构函数时要调用基类析构函数) 。 virtual ~ABC() = 0;//声明 virtual ABC::~ABC(){};//定义条款8:别让异常逃离析构函数
当存在多个对象同时销毁时,可能存在多个对象在析构部分抛出异常 。程序将结束执行或者导致不明确行为 。 析构函数中发生异常的两种处理方式:
(1)结束程序:可以阻止异常从析构函数传播出去(下例中为析构函数中调用的close()可能出现异常)
(2)吞下异常
:有时候希望程序即使遇到异常以后也要让程序继续执行一段时间(例如数据保存工作等等) 。
析构函数绝对不要吐出异常 。如果一个被析构函数调用的函数可能抛出异常 ,析构函数应该捕捉任何异常 ,然后吞下它们(不传播)或结束程序。 如果客户需要对某个操作函数运行期间抛出的异常做出反应 ,那么class应该提供一个普通函数(而且在析构函数中)执行该操作 。
条款9:绝不在构造和析构过程中调用virtual函数
构造和析构函数中使用虚函数可能不会产生多态行为 ,与预期不同 。base class构造期间virtual函数绝不会下降到derived class阶层。
原因:在派生类对象的基类构造期间 ,对象的类型是基类而不是派生类 ,此时执行的虚函数是基类的虚函数而不是派生类的虚函数 。对象在派生类构造函数开始执行之前不会成为一个派生类对象 。析构函数同理。 有一种情况下很难侦测到:基类构造函数没有直接调用虚函数 ,而是调用了将虚函数封装到一个非virtual的函数中 ,此时依然会发生构造基类部分调用基类的虚函数的情况 ,但是难以检测 。因此,在构造和析构期间不要调用虚函数 ,包括它们所调用的函数也不要存在调用虚函数 ,因为这类调用从不下降至派生类 。 如果需要不同派生类在构造时,实现相对应的功能 。解决办法就是让基类实现对应功能的函数为非virtual ,派生类在构造中传递实现对应功能的信息给基类构造函数 ,基类构造函数将信息给事项对应功能的非virtual函数 。通常这些携带传递信息的变量或者函数被设为static ,保证使用的变量已经初始化。由于派生类成员构造在基类成员构造之后 ,因此如果不用static成员传递信息 ,派生类其他成员可能是未初始化的 。
条款10:令operate=返回一个reference to *this
为了实现“连锁赋值 ” ,赋值操作符必须返回一个引用指向操作符左侧实参 。此条款也适用于其他赋值相关运算 ,例如+= ,-= ,*=等等 。 X = Y = C = 15;//连锁赋值条款11:在operate=中处理“自我赋值 ”
自我赋值类似如下:
自我赋值可能会产生不安全的情况 。如下例所示 ,如果是同一对象 ,delete销毁是this->pb所指向的区域 ,同时是rhs.pb所指向的区域,那么自我赋值后返回的对象中的pb指针指向的是一个已删除的区域。
解决方法:
(1)正同测试:
(2)创建副本或者以by value方式接收传入的实参(也是一份副本) ,这样赋值操作的两个对象/成员变量本质上是两个数据内容可能一样但实际是两个单独的对象/成员变量 ,copy and swap方式:
确保当对象自我赋值时operator有良好的行为 。其中技术包括比较“来源对象 ”和“目标对象 ”的地址 、精心周到的语句顺序,以及copy and swap 。确定任何函数如果操作一个以上的对象 ,而其中多个对象是同一个对象时 ,其行为仍然正确。条款12:复制对象时勿忘其每一个成分
当为class添加一个成员变量,你必须同时修改copying函数(所有的拷贝构造以及operator=函数) 。 如果要为derived class 撰写copying函数 ,必须小心复制其base class成分 。如果是private成员 ,应该让derived class 的copying函数调用相应的base class函数。确保**(1)复制所有local成员变量;(2)调用所有基类内的适当的copying函数** 。
不要尝试以某一个copying函数实现另一个copying函数 。应该将共同重复机能放进第三个函数中 ,并由两个copying函数共同调用 。创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!