Effective C++ (笔记) : 条款05 -- 条款10

条款05:了解C++默默编写并调用哪些函数

编译器可以暗自为class创建default构造函数、copy构造函数、copy
assignment
操作符,以及析构函数。

只有这些函数需要(被调用)时,它们才会被编译器创建出来。在编译器产生的复制构造函数和赋值运算符执行的都是浅拷贝。当数据成员是引用或者常量的时候,编译器不知道该怎么处理,两手一摊,无能为力。

当某个基类将copy assignment操作符声明为私有的,编译器将拒绝为派生类生成copy
assignment
操作符,因为它无权(也不能)处理基类部分。

条款06:若不想使用编译器自动生成的函数,就应该明确拒绝

在一些类中不允许拷贝和赋值,因为这是不允许和无意义的。因此可以将拷贝和赋值声明为private,这样当客户端调用的时候编译器会阻挠它。同时,成员函数和友元函数能调用,但是在链接的时候会报错。因此这种方法能明确拒绝我们不想编译器自动生成的函数。

将链接期错误提前到编译期是个不错的注意,因为这会更早发现问题,不会在联调的时候才暴漏问题。在一个基类中将这些函数声明为私有的,那么当前类(即派生类)便不再生成这些函数。达到了目的。这些“编译期生成版”会尝试调用其base
class
的对应兄弟,那些调用会被编译期拒绝,因为其base class的拷贝函数时private

条款07:为多态基类声明Virtual析构函数

基类指针指向一个派生类对象,而这个对象却经由基类指针被删除,而目前的基类有一个non-virtual析构函数。想想会发生什么?

结果将是未定义的,实际执行时通常发生的是对象的derived成分没有被销毁。基类部分能够销毁,这是形成资源泄露和调试时间浪费的一个原因。

解决这个问题的方式很简单,只要为基类创建一个virtual析构函数即可。

如果class不含virtual函数,通常表示它并不愿意被用作一个base
class
。欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数该被调用。无端的将所有classes的析构函数声明为virtual,也是错误的。心得是:只有当class内含至少一个virtual函数,才为它声明virtual函数。

带多态性质的基类应该声明一个virtual析构函数,如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

Classes的设计目的如果不是作为base
classes
使用,或者不是为了多态性质,就不应该声明virtual析构函数。

条款08:别让异常逃离析构函数

如果有一个类负责数据库的连接,那么将释放连接的操作放在析构函数中是一个不错的主意。前提是释放操作不会抛出异常,如果释放操作可能抛出异常那么我们应该做的更过以避免这种情况。

  1. DB::~DBConn()
  2. {
  3. try{ db.close(); }
  4. catch(...){
  5. 制作运转记录,记下对close的调用失败
  6. }

通常情况下,将异常吞掉是个坏主意,因为它因为它压制了“某些动作失败”的重要信息。

一个较佳的策略是重新设计DBConn的接口,使客户有机会对可能出现的问题做出反应。比如可以添加一个close函数,供客户显示调用,赋予客户一个得以处理“因该操作而引发的异常”的机会。让客户有时间相应可能出现的错误。在析构函数中,我们再检测是否成功关闭,如果客户不显示关闭连接,那么也就是他们认为不会发生关闭错误,那么就忽略它,让析构函数去释放就好,即使有错误发生,那么也是无关紧要的。

  1. class DBConn{
  2. public:
  3. void close()
  4. {
  5. db.close();
  6. closed = true;
  7. }
  8. ~DBConn()
  9. {
  10. if(!closed){
  11. try{
  12. db.close();
  13. }
  14. catch(...){
  15. 制作运转记录,记下对close的调用失败
  16. }
  17. }
  18. }
  19. };
  20. private:
  21. DBConnection db;
  22. bool closed;
  23. };

析构函数绝对不要突出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该能够捕获任何catch(...)异常,然后吞下(不传播)它们或结束程序。

如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而不是在析构函数中)执行该操作。

条款09:绝不在构造和析构过程中调用Virtual函数

派生类对象内的基类部分会在派生类自身成分被构造之前先构造妥当。在构造函数内调用虚函数将是基类中的版本,不是派生类的版本---即使目前即将建立的对象类型是派生类。是的,在基类构造期间虚函数绝不会下降到派生类的阶层。非正式的算法比较传神:在基类构造期间,虚函数不是虚函数。

相同的情况也适用于析构函数,一旦派生类析构函数开始执行,对象内的派生类成员变量将呈现未定义的值,所以C++视它们仿佛不再存在。

有时候发现这个隐藏的问题比较晦涩难懂,因为析构函数可能没有直接调用虚函数,而是通过其他函数间接调用虚函数,程序能运行,但是结果却不是你想要的。要调试发现这样的问题将是耗神费力的。唯一能做的就是避免这样的事情发生。

如果有这样的业务需求又应该怎么办呢?一种做法就是在基类中将函数改为non-virtual,然后要求派生类构造函数传递必要信息(参数)给基类的构造函数。

换句话说,由于你无法使用虚函数从基类向下调用,在构造期间,你可以藉由"令派生类将必要的构造信息向上传递至基类的构造函数"替换之而加以弥补。

条款10:令 opreator= 返回一个 reference
to *this

为了实现“连锁赋值”这是必须的。

+=*=-=中也要使用这个协议。

要使重载的运算符表现出内置类型和使用习惯上的一致性。

时间: 2024-11-05 21:39:42

Effective C++ (笔记) : 条款05 -- 条款10的相关文章

Effective C++笔记05:实现

条款26:尽可能延后变量定义式的出现时间 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 有些对象,你可能过早的定义它,而在代码执行的过程中发生了导常,造成了开始定义的对象并没有被使用,而付出了构造成本来析构成本. 所以我们应该在定义对象时,尽可能的延后,甚至直到非得使用该变量前一刻为止,应该尝试延后这份定义直到能够给它初值实参为止. 这样做的好处是:不仅可以避免构造(析构)非必要对象,还可以避免无意义的default构造行为. 遇到循环怎么办?此时往往我

Effective C++笔记:构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的. 当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数:当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝 将拷贝构

Effective C++笔记04:设计与声明

条款18:让接口容易被正确使用,不易被误用 1,好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. 2,"促进正使用"的办法包括接口的一致性,以及与内置类型的行为兼容. 3,"阻止误用"的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. 4,shared_ptr支持定制型删除器.这可以防范DLL问题,可以用来自动解除互斥锁. 条款19:设计class犹如设计type 博客地址:http://blog.csd

Effective C++笔记06:继承与面向对象设计

关于OOP 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private,也可以是virtual或non-virtual. 2,成员函数的各个选项:virtual或non-virtual或pure-virtual. 3,成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有如些?如果class的行为

Effective C++ 笔记二 构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数 编译器默认声明一个default构造函数.一个copy构造函数.一个copy assignment操作符和一个析构函数.这些函数都是public且inline. 1 class Empty { 2 public: 3 Empty() {...} 4 Empty(const Empty& rhs) {...} 5 ~Empty() {...} 6 Empty& operator=(const Empty& rhs) {...} 7 }; 如

Effective c++(笔记) 之 杂项讨论

看到了Effective c++的最后一章,最开始的那章---内存管理还没搞清楚,准备那章搞清楚完也写篇博客,不管怎样,有好的开始就应该让它有个完美的结束,杂项讨论这章是作者将那些分到哪章都不合适的就索性放到了最后讨论,我看完后从中摘出自己认为重要的坐下笔记,如果能帮得到大家,那就更荣幸了哈! 1.当我们定义一个类时,编译器会自动给我产生哪些成员函数? 解析:我们都知道,当我们定义类时,如果我们没有定义某些成员函数的话,编译器会总会给我们自动合成,这就是编译器默默为我们完成和调用函数,这些函数主

Effective c++(笔记)之继承关系与面向对象设计

1.公有继承(public inheritance) 意味着"是一种"(isa)的关系 解析:一定要深刻理解这句话的含义,不要认为这大家都知道,本来我也这样认为,当我看完这章后,就不这样认为了. 公有继承可以这样理解,如果令class D以public 的形式继承了class B ,那么可以这样认为,每一个类型为D的对象同时也可以认为是类型为B的对象,但反过来是不成立的,对象D是更特殊化更具体的的概念,而B是更一般化的概念,每一件事情只要能够施行于基类对象身上,就一定可以应用于派生类对

[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+1}" 反射获取函数源代码的功能很强大,使用函数对象的toString方法有严重的局限性.toString方法的局限性ECMAScript标准对函数对象的toString方法的返回结果(即该字符串)并没有任何要求.这意味着不同的js引擎将产生不同的字符串,甚至产生的字符串与该函数并不相关. 如果函数

驱动开发读书笔记. 0.05 linux 2.6 platform device register 平台设备注册 2/2 共2篇

驱动开发读书笔记. 0.05 linux 2.6 platform device register 平台设备注册 2/2 共2篇 下面这段摘自 linux源码里面的文档 : 内核版本2.6.22Documentation/driver-model/platform.txt找到一篇译文:http://blog.csdn.net/yili_xie/article/details/5193609 Device Enumeration 82 ~~~~~~~~~~~~~~~~~~ 83 As a rule