《Effective C++》读书笔记(二) 构造/析构/赋值运算 (第一部分)
来源:互联网 发布:如何在淘宝卖二手货 编辑:程序博客网 时间:2024/05/17 23:56
构造/析构/赋值运算
Constructors,Destructors,and Assignment Operators
条款05:了解C++默默编写并调用哪些函数
Know what functions C++ silently writes and calls.
当我们写下一个empty class时,不包含任何我们声明的constructors,destructors,以及copy assignment操作符时,就像这样:
class Empty {};
看起来什么都没做,其实编译器已经帮我们体贴地做了不少。就像这样:
class Empty{public: Empty() {...} //default构造函数 Empty(const Empty& rhs) {...} //copy构造函数 ~Empty() {...} //析构函数 Empty& operator=(const Empty& rhs) {...} //copy assignment 操作符};
default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用base classes和non-static成员变量的构造函数和析构函数。而且,编译器默认生成的destructors是个non-virtual,除非这个class的基类(base class)自身声明有virtual destructors。
当然了,如果我们主动地去声明了constructors,destructors,以及copy assignment操作符,那么编译器就会带领它的default构造函数、copy构造函数、析构函数和copy assignment操作符退居二线,也就没必要再去自动创建,来遮蔽我们主动声明的版本了。
☆编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。
条款06:若不想使用编译器自动生成的函数,就该明确拒绝
Explicit disallow the use of compiler-generated functions you do not want.
书上有个例子:地产商中介卖的是房子,一个中介软件系统自然而然想有个class来描述待售房屋。而房屋是独一无二的。就像这样~
class HomeForSale {...}
为这房屋去创建一个副本显然不合适。也就是说,各种蓄谋对此房屋进行copy的行为,都应该以失败收场,理想情况应该是这样:
HomeForSale h1;HomeForSale h2;HomeForSale h3(h1); //企图复制h1----不该通过编译h2=h1; //还是企图复制h1----也不该通过编译
然而。理想很丰满,现实很骨感。依据上边所说的条款5,就算我们不去声明,只要有人想去调用它们,编译器还是会去帮这个忙。再说清楚些,如果不声明copy构造函数或copy assignment操作符,编译器会帮忙生成一份默认的;如果声明了,所写的class还是支持copying。而在这个例子的目标,是去阻止copying!
解决之道很简单:将copy构造函数和copy assignment操作符声明在private中并且故意不实现。也就是“将成员函数声明为private而且故意不实现它们”的伎俩。就算member函数和friend函数还是能调用private函数,也会发生连接错误(linkage error)。就像这样:
class HomeForSale{public: ...private: ... HomeForSale(const HomeForSale&); //只有声明 HomeForSale& operator=(const HomeForSale&);};
不过有一种更好的方法,就是把连接期错误移到编译期,越早侦测到错误越好。只要任何人——甚至member函数或friend函数去尝试复制HomeForSale对象,编译器就会试着生成一个copy构造函数和一个copy assignment操作符,而编译器生成版又会去尝试调用其base class的对应兄弟,最后只会被拒绝。因为base class的copy函数不是public或protected,而是private。
class Uncopyable{protected: //允许derived对象构造或析构 Uncopyable() {} ~Uncopyable() {}private: Uncopyable(const Uncopyable&); //但阻止copying Uncopyable& operator=(const Uncopyable&);};...class HomeForSale:private Uncopyable{ ... //class不再声明copy构造函数或copy assignment操作符};
☆为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。
条款07:为多态基类声明virtual析构函数
Declare destructors virtual in polymorphic base classes.
当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,实际执行时通常发生的是对象的derived成分没被销毁。形如这样的classes:
class TimeKeeper{public: TimeKeeper(); ~TimeKeeper(); ...};class AtomicClock: public TimeKeeper {...};class WaterClock: public TimeKeeper {...};class WristWatch: public TimeKeeper {...};
消除这个问题的做法很简单:给base class一个virtual析构函数,之后删除derived class对象就会是我们期望的。对,它会销毁整个对象,包括所有derived class成分,就像这样:
class TimeKeeper{public: TimeKeeper(); virtual ~TimeKeeper(); ...};TimeKeeper* ptk=getTimeKeeper();...delete ptk;
析构函数的运作方式是:最深层派生(most derived)的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。
声明virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数该被调用。主要是由vptr(virtual table point)指针指出。如果滥用virtual函数,就会相应增加vptr的使用,也就会引起空间的浪费;并且class不含virtual函数时,通常表示它并不意图被用作一个base class。当class不企图被当做base class时,令其析构函数为virtual往往是个馊主意。比如std::string和STL容器就不被设计作为base classes使用,其中内部的是个non-virtual函数,如果将其当成base class,会因为derived成分没被销毁而导致行为不明确。
无端地将所有classes的析构函数都声明为virtual,就像从未声明它们为virtual一样,都是错误的。
☆polymorphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
☆Classes的设计目的如果不是作为base classes使用,或不是为了具备多态行(polymorphically),就不该声明为virtual函数。
条款08:别让异常逃离析构函数
Prevent exceptions from leaving destructors.
C++并不禁止析构函数吐出异常,但它不鼓励这样做。在析构函数内吐出异常,也就是说,让异常逃离了析构函数,就很容易引起不明确行为。
有两种方法可以避免这种问题。第一种是让析构函数记录内部函数的调用失败,然后不传播或者强行结束程序。
DBConn::~DBConn(){ try {db.close(); } catch (...) { 制作运转记录,记下对close的调用失败; }}
DBConn::~DBConn(){ try {db.close(); } catch (...) { 制作运转记录,记下对close的调用失败; std::abort(); }}但如果析构函数内部的close()抛出异常呢?
第二种方法是重新设计DBConn接口,使其客户有机会对可能出现的问题进行反应。
class DBConn{public: ... void close() { db.close(); closed=true; }~DBConn{ if (!closed) { try { db.close(); } catch (...) { 制作运转记录,记下对close的调用失败; ... } }}private: DBConnection db; bool closed;};如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以为的某个函数。由客户自己调用close并不会对他们带来负担,而是给他们一个处理错误的机会,否则他们没机会相应。
☆析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
☆如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
参考文献:
《Effective C++》3rd Scott Meyers著,侯捷译
- 《Effective C++》读书笔记(二) 构造/析构/赋值运算 (第一部分)
- 《Effective C++》读书笔记(三) 构造/析构/赋值运算 (第二部分)
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之二)
- Effective C++(二)构造/析构/赋值运算
- 【读书笔记】构造、析构、赋值(Effective C++)
- Effective C++(二)构造/析构/赋值运算
- Effective C++读书笔记 第二部分 构造/析构/赋值运算
- 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记
- effective c++读书笔记二——构造/析构/赋值运算
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之一)
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之三)
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之四)
- Effective C++读书笔记(二)构造、析构、赋值
- Effective C++笔记: 构造/析构/赋值运算(二)
- Effective C++读书笔记---构造/析构/赋值运算
- 《Effective C++》读书笔记(第一部分)
- Effective C++ <二>:构造,析构,赋值运算
- effective c++-构造/析构/赋值运算
- 在VS环境下使用SVN
- 使用匿名函数减少重复代码
- AMF学习1数据类型
- google calendar API 插入 event
- AMF学习2远程调用的封装
- 《Effective C++》读书笔记(二) 构造/析构/赋值运算 (第一部分)
- CSS完美兼容IE6/IE7/FF的通用方法
- .NET牛人应该知道些什么,我的回答
- 使用SQLServer2005的链接服务器链接Sybase数据库
- 使用C#实现网站用户登录
- 一定时间间隔执行某个方法
- 使用C#登录带验证码的网站
- 关于ADO.NET连接池
- 一款.net性能分析工具