Effective C++笔记
文章目录
effecitve c++ 笔记
Ch-1
Item 1: view C++ as a federation of languages
c++继承了c的部分,增加了面向对象的设计,增加了模板,以及标准模板库
这四个部分可以看做四种子语言,相对独立
Item 2: Prefer consts, enums, and inlines to #defines
- 使用const变量替换掉#define,后者只是做简单替换,对编译器透明,可能会导致生成重复对象代码(obj文件?)
- 相对于双重const的指针的char*字符串,更推荐使用const限定的string对象
-
类成员常量(部分如int, char, bool等内置类型)如果不被取址,可以只声明,不定义
1 2 3 4 5class GamePlayer { private: static const int NumTurns = 5; // constant declaration int scores[NumTurns]; // use of constant };如果需要取址,需要在实现文件中定义:
1const int GamePlayer::NumTruns;除此之外,可以使用枚举变量替换常量,以兼容一些老的编译器,以支持用作数组声明:
1 2 3 4 5 6class GamePlayer { private: enum { NumTurns = 5 }; // "the enum hack" — makes // NumTurns a symbolic name for 5 int scores[NumTurns]; // fine }; - 使用内联模板函数替代#define实现的宏函数,以降低复杂度
Item 3: Use const whenever possible
- 指针*之前的cosnt限定指向的数据,*之后的const限定指针变量本身
-
迭代器的const等价于T* const,而const_iterator等价于const* T
1 2 3 4 5 6 7const std::vector<int>::iterator iter = vec.begin(); *iter = 10; // OK, changes what iter points to ++iter; // error! iter is const std::vector<int>::const_iterator cIter = vec.begin(); *cIter = 10; // error! *cIter is const ++cIter; // fine, changes cIter
const 成员函数
- bitwise constness(physical constness)含义是成员函数不应该修改对象的数据成员
-
logical constness在不违背逻辑的情况下,可以修改部分数据成员(使用mutable修饰)
这两种constness是我们理解层面的概念,c++的const修饰符保证了大部分情况bitwise;如果要实现logical constness,需要使用mutable修饰符,让对应变量可以在const函数中被修改
在一些不好的实现中,使用const修饰符还是不能保证bitwise:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16class CTextBlock { public: char& operator[](std::size_t position) const { // inappropriate (but bitwise const) // declaration of operator[] return pText[position]; } private: char *pText; }; const CTextBlock cctb("Hello"); char *pc = &cctb[0]; *pc = 'J';
避免在const和非const成员函数中的代码重复
很多时候我们需要分别实现const和non-const版本的成员函数,这里面可能包含很多重复代码(比如debug相关代码、日志代码、数据完整性验证)
通常情况下我们可以在non-const函数中调用const函数,反过来不行:
|
|
Item 4: Make sure that objects are initialized before they're used
- 为简单处理,所有的变量都需要初始化,优先使用构造函数列表来初始化成员函数(老的编译器需要程序员保证顺序)
- 函数内的static变量可以当做单例使用
Ch-2
Item 5: Know what functions C++ silentlywrites and calls
如果没有声明,编译器会隐式生成构造、拷贝构造和析构函数。但如果遇到一些意外情况,编译器会报错:
- 成员变量是引用或者带有const修饰,不会生成拷贝构造函数
- 基类拷贝构造函数被声明为private
- 等等
Item 6: Explicitly disallow the use of compiler-generated functions you do not want
如果不希望编译器自动生成部分函数,可以在private下面声明他们,然后不实现
特别的,如果一个类不希望被拷贝,可以考虑私有继承 Uncopyable 类:
|
|
Item 7: Declare destructors virtual in polymorphic base classes
- 需要用作多态的类型的类需要虚析构函数
- 不用于派生的类不建议使用虚析构函数,一方面增加了一个虚表的开销,另一方面数据结构和c等外部语言不兼容
Item 8: Prevent exceptions from leaving destructors
- 析构函数不应该异常,如果发生了,要么终止程序,要么吞掉所以异常
- 某些可能抛异常的清理操作尽量放在独立的函数(类似于close或者destroy这样的),由外部调用并处理异常
Item 9: Never call virtual functions during construction or destruction
对象构造和析构的时候调用虚函数会存在无定义行为,通常这种需求可以转换为在构造时传递参数或者使用对应类的静态构建函数
Item 10: Have assignment operators return a reference to *this
赋值操作符最好返回*this(所有内置类型都遵守这个惯例),连等的操作就是通过这样实现的
Item 11: Handle assignment to self in operator=
赋值操作符需要考虑到自我赋值的情况,一般来说构建一个临时变量,然后swap
也可以做分支判断,如果是自己就直接返回。但一般这种操作是低频的,而且会带来取址、缓存命中和管线的开销。
Item 12: Copy all parts of an object
拷贝函数包括构造拷贝函数和赋值操作符,如果要自己实现拷贝函数,需要覆盖掉所有可能的情况(特别是在后面增加成员的时候),编译器是不会报错的
两个拷贝函数无法相互调用,要实现代码复用,只能在增加一个private的公共代码函数
Ch-3 Resource Management
资源就是一种一旦你使用了,就需要返回(给系统)的东西。如果不还,就会出问题
Item 13: Use objects to manage resources.
通常从API申请到资源之后,返回的是指针(或者句柄),例如:
|
|
函数f中第一行申请了pInv,最后一行释放了pInv。在中间的使用过程中,如果出现了异常,最后一行可能执行不到,一个比较好的办法就是把pInv放到局部对象来管理,在栈清理时候自动调用对象析构释放资源,auto_ptr就是这个作用:
|
|
由于auto_ptr在拷贝时候的语义很奇怪,后面被废弃了,而是使用unique_ptr替代,unique_ptr不允许拷贝,只允许使用std::move显式转移,并且支持deleter和数组
这种资源管理方式经常叫做RAII(Resource Acquisition Is Initialization)
unique_ptr独占一个资源,如果要共享,可以使用shared_ptr。shared_ptr使用引用计数来维护声明周期,在引用计数降为0时,调用deleter
引用计数会带来循环引用的问题,所以可以考虑使用弱引用(weak_ptr)来解决,weak_ptr在使用时必须先转为shared_ptr
Item 14: Think carefully about copying behavior in resource-managing classes.
一般来说涉及资源管理拷贝的情况分为四种:
-
禁止拷贝 比如说锁,拿到一把锁之后,极少可能性会把锁的资源管理类进行复制:
1 2 3 4 5 6 7 8 9class Lock { public: explicit Lock(Mutex *pm) : mutexPtr(pm) { lock(mutexPtr); } // acquire resource ~Lock() { unlock(mutexPtr); } // release resource private: Mutex *mutexPtr; }; -
对下层资源计算引用计数 当拷贝资源管理类时,增加一个引用计数,类似于shared_ptr。当引用计数降为0时,调用deleter做清理操作
1 2 3 4 5 6 7 8 9 10class Lock { public: explicit Lock(Mutex *pm) // init shared_ptr with the Mutex : mutexPtr(pm, unlock) // to point to and the unlock func { // as the deleter lock(mutexPtr.get()); // see Item 15 for info on "get" } private: std::tr1::shared_ptr<Mutex> mutexPtr; // use shared_ptr }; // instead of raw pointer - 拷贝下层资源 如果希望复制多份资源时候,就需要拷贝下层资源,各个资源管理类需要自行清理掉自己管理的资源副本,简称“深拷贝”,std::string就是这样做的
- 转移下层资源的拥有权 类似于auto_ptr或者unique_ptr这样的独占管理类
Item 15: Provide access to raw resources in resource-managing classes.
资源管理类都会提供API访问下层资源,c++支持显式或者隐式,前者安全,后者方便:
|
|