- 浏览: 1410135 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
sdgxxtc:
[quo[color=red]te][/color]
C#使用OleDb读取Excel,生成SQL语句 -
zcs302567601:
博主,你好,一直都有个问题没有搞明白,就是 2.x的版本是通过 ...
NGUI所见即所得之UIPanel -
一样的追寻:
感谢楼主!
有向强连通和网络流大讲堂——史无前例求解最大流(最小割)、最小费用最大流 -
cp1993518:
感谢!从你的博客里学到了很多
Unity日志工具——封装,跳转 -
cp1993518:
学习了~,话说现在的版本custom还真的变委托了
NGUI所见即所得之UIGrid & UITable
转载自http://bellgrade.blog.163.com/blog/static/83155959200863113228254/,方便日后自己查阅,
More Effective C++读书笔记
条款1:指针与引用的区别
二者之间的区别是:在任何情况下都不能用指向空值的引用,而指针则可以;指针可以被重新赋值以指向另一个不同的对象,但是引用则总是指向在初始化时被指定的对象,以后不能改变
在以下情况下使用指针:一是存在不指向任何对象的可能性;二是需要能够在不同的时刻指向不同的对象
在以下情况使用引用:总是指向一个对象且一旦指向一个对象之后就不会改变指向;重载某个操作符时,使用指针会造成语义误解
条款2:尽量使用C++风格的类型转换
static_cast:功能上基本上与C风格的类型转换一样强大,含义也一样但是不能把struct转换成int类型或者把double类型转换成指针类型另外,它不能从表达式中去除const属性
const_cast:用于类型转换掉表达式的const或volatileness属性但是不能用它来完成修改这两个属性之外的事情
dynamic_cast:用于安全地沿着类的继承关系向下类型转换失败的转换将返回空指针或者抛出异常
reinterpret_cast:这个操作符被用于的类型转换的转换结果时实现时定义因此,使用它的代码很难移植最普通的用途就是在函数指针之间进行转换
条款3:不要使用多态性数组
多态和指针算法不能混合在一起使用,所以数组和多态也不能用在一起
数组中各元素的内存地址是数组的起始地址加上之前各个元素的大小得到的,如果各元素大小不一,那么编译器将不能正确地定位元素,从而产生错误
条款4:避免无用的缺省构造函数
没有缺省构造函数造成的问题:通常不可能建立对象数组,对于使用非堆数组,可以在定义时提供必要的参数另一种方法是使用指针数组,但是必须删除数组里的每个指针指向的对象,而且还增加了内存分配量
提供无意义的缺省构造函数会影响类的工作效率,成员函数必须测试所有的部分是否都被正确的初始化
条款5:谨慎定义类型转换函数
缺省的隐式转换将带来出乎意料的结果,因此应该尽量消除,使用显式转换函数通过不声明运算符的方法,可以克服隐式类型转换运算符的缺点,通过使用explicit关键字和代理类的方法可以消除单参数构造函数造成的隐式转换
条款6:自增和自减操作符前缀形式与后缀形式的区别
后缀式有一个int类型参数,当函数被调用时,编译器传递一个0作为int参数的值传递给该函数可以在定义时省略掉不想使用的参数名称,以避免警告信息
后缀式返回const对象,原因是 :使该类的行为和int一致,而int不允许连续两次自增后缀运算;连续两次运算实际只增一次,和直觉不符
前缀比后缀效率更高,因为后缀要返回对象,而前缀只返回引用另外,可以用前缀来实现后缀,以方便维护
条款7:不要重载&&,||,或者,
对 于以上操作符来说,计算的顺序是从左到右,返回最右边表达式的值如果重载的话,不能保证其计算顺序和基本类型想同操作符重载的目的是使程序更容易阅 读,书写和理解,而不是来迷惑其他人如果没有一个好理由重载操作符,就不要重载而对于&&,||和,,很难找到一个好理由
条款8:理解各种不同含义的new和delete
new操作符完成的功能分两部分:第一部分是分配足够的内存以便容纳所需类型的对象;第二部分是它调用构造函数初始化内存中的对象new操作符总是做这两件事,我们不能以任何方式改变它的行为
我们能改变的是如何为对象分配内存new操作符通过调用operator new来完成必需的内存分配,可以重写或重载这个函数来改变它的行为可以显式调用operator来分配原始内存
如果已经分配了内存,需要以此内存来构造对象,可以使用placement new,其调用形式为new(void* buffer)class(int size)
对于delete来说,应该和new保持一致,怎样分配内存,就应该采用相应的办法释放内存
operator new[]与operator delete[]和new与delete相类似
条款9:使用析构函数防止资源泄漏
使用指针时,如果在delete指针之前产生异常,将会导致不能删除指针,从而产生资源泄漏
解决办法:使用对象封装资源,如使用auto_ptr,使得资源能够自动被释放
条款10:在构造函数中防止资源泄漏
类中存在指针时,在构造函数中需要考虑出现异常的情况:异常将导致以前初始化的其它指针成员不能删除,从而产生资源泄漏解决办法是在构造函数中考虑异常处理,产生异常时释放已分配的资源最好的方法是使用对象封装资源
条款11:禁止异常信息传递到析构函数外
禁止异常传递到析构函数外的两个原因:第一能够在异常传递的堆栈辗转开解的过程中,防止terminate被调用;第二它能帮助确保析构函数总能完成我们希望它做的所有事情
解决方法是在析构函数中使用try-catch块屏蔽所有异常
条款12:理解抛出一个异常与传递一个参数或调用一个虚函数间的差异
有 三个主要区别:第一,异常对象在传递时总被进行拷贝当通过传值方式捕获时,异常对象被拷贝了两次对象作为参数传递给函数时不需要被拷贝;第二,对象作 为异常被抛出与作为参数传递给函数相比,前者类型转换比后者少(前者只有两种转换形式:继承类与基类的转换,类型化指针到无类型指针的转换);最后一点, catch子句进行异常类型匹配的顺序是它们在源代码中出现的顺序,第一个类型匹配成功的擦他处将被用来执行当一个对象调用一个虚函数时,被选择的函数 位于与对象类型匹配最佳的类里,急事该类不是在源代码的最前头
条款13:通过引用捕获异常
有三个选择可以捕获异常:第一指 针,建立在堆中的对象必需删除,而对于不是建立在堆中的对象,删除它会造成不可预测的后果,因此将面临一个难题:对象建立在堆中还是不在堆中;第二传 值,异常对象被抛出时系统将对异常对象拷贝两次,而且它会产生对象切割,即派生类的异常对象被作为基类异常对象捕获时,它的派生类行为就被切割调了 这样产生的对象实际上是基类对象;第三引用,完美解决以上问题
条款14:审慎使用异常规格
避免调用unexpected函数 的办法:第一避免在带有类型参数的模板内使用异常规格因为我们没有办法知道某种模板类型参数抛出什么样的异常,所以不可能为一个模板提供一个有意义的 异常规格;第二如果在一个函数内调用其它没有异常规格的函数时应该去除这个函数的异常规格;第三处理系统本身抛出的异常可以将所有的 unexpected异常都被替换为自定义的异常对象,或者替换unexpected函数,使其重新抛出当前异常,这样异常将被替换为 bad_exception,从而代替原来的异常继续传递
很容易写出违反异常规格的代码,所以应该审慎使用异常规格
条款15:了解异常处理的系统开销
三 个方面:第一需要空间建立数据结构来跟踪对象是否被完全构造,还需要系统时间保持这些数据结构不断更新;第二try块无论何时使用它,都得为此付出 代价编译器为异常规格生成的代码与它们为try块生成的代码一样多,所以一个异常规格一般花掉与try块一样多的系统开销第三抛出异常的开销因为 异常很少见,所以这样的事件不会对整个程序的性能造成太大的影响
条款16:牢记8020准则
8020准则说的是大约20%的代码使用了80%的程序资源,即软件整体的性能取决于代码组成中的一小部分使用profiler来确定程序中的那20%,关注那些局部效率能够被极大提高的地方
条款17:考虑使用懒惰计算法
懒惰计算法的含义是拖延计算的时间,等到需要时才进行计算其作用为:能避免不需要的对象拷贝,通过使用operator[]区分出读写操作,避免不需要的数据库读取操作,避免不需要的数字操作但是,如果计算都是重要的,懒惰计算法可能会减慢速度并增加内存的使用
条款18:分期摊还期望的计算
核心是使用过度热情算法,有两种方法:缓存那些已经被计算出来而以后还有可能需要的值;预提取,做比当前需要做的更多事情
当必须支持某些操作而不总需要其结果时,可以使用懒惰计算法提高程序运行效率;当必须支持某些操作而其结果几乎总是被需要或不止一次地需要时,可以使用过度热情算法提高程序运行效率
条款19:理解临时对象的来源
临时对象产生的两种条件:为了是函数成功调用而进行隐式类型转换和函数返回对象时
临时对象是有开销的,因此要尽可能去消除它们,然而更重要的是训练自己寻找可能建立临时对象的地方在任何时候只要见到常量引用参数,就存在建立临时对象而绑定在参数上的可能性在任何时候只要见到函数返回对象,就会有一个临时对象被建立(以后被释放)
条款20:协助完成返回值优化
应当返回一个对象时不要试图返回一个指针或引用
C+ +规则允许编译器优化不出现的临时对象,所有最佳的办法莫过于:retrun Ratinal(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator())这种优化是通过使用函数的retuan location(或者用在一个函数调用位置的对象来替代),来消除局部临时对象,这种优化还有一个名字:返回值优化
条款21:通过重载避免隐式类型转换
隐式类型转换将产生临时对象,从而带来额外的系统开销
解决办法是使用重载,以避免隐式类型转换要注意的一点是在C++中有一条规则是每一个重载的operator必须带有一个用户定义类型的参数(这条规定是有道理的,如果没有的话,程序员将能改变预定义的操作,这样做肯定吧程序引入混乱的境地)
另外,牢记8020规则,没有必要实现大量的重载函数,除非有理由确信程序使用重载函数后整体效率会有显著提高
条款22:考虑用运算符的赋值形式取代其单独形式
运算符的赋值形式不需要产生临时对象,因此应该尽量使用对运算符的单独形式的最佳实现方法是return Rational(lhs) += rhs;这种方法将返回值优化和运算符的赋值形式结合起来,即高效,又方便
条款23:考虑变更程序库
程序库必须在效率和功能等各个方面有各自的权衡,因此在具体实现时应该考虑利用程序库的优点例如程序存在I/O瓶颈,就可以考虑用stdio替代iostream
条款24:理解虚拟函数多继承虚基类和RTTI所需的代价
虚函数所需的代价:必须为每个包含虚函数的类的virtual table留出空间;每个包含虚函数的类的对象里,必须为额外的指针付出代价;实际上放弃了使用内联函数
多继承时,在单个对象里有多个vptr(一个基类对应一个)它和虚基类一样,会增加对象体积的大小
RTTI能让我们在运行时找到对象和类的有关信息,所以肯定有某个地方存储了这些信息,让我们查询这些信息被存储在类型为type_info的对象里,可以通过typeid操作符访问到一个类的typeid对象通常,RTTI被设计为在类的vbtl上实现
条款25:将构造函数和非成员函数虚拟化
构 造函数的虚拟化看似无意义,但是在实际当中有一定的用处例如,在类中构建一个虚拟函数,其功能仅仅是实现构造函数,就可以对外界提供一组派生类的公共构 造接口虚拟拷贝构造函数也是可以实现的,但是要利用到最近才被采纳的较宽松的虚拟函数返回值类型规则被派生类重定义的虚拟函数不用必须与基类的虚拟函 数具有一样的返回类型
具有虚拟行为的非成员函数很简单首先编写一个虚拟函数完成工作,然后再写衣一个非虚拟函数,它什么也不做只是调用这个函数,可以使用内联来避免函数调用的开销
条款26:限制某个类所能产生的对象数量
只 有一个对象:使用单一模式,将类的构造函数声明为private,再声明一个静态函数,该函数中有一个类的静态对象不将该静态对象放在类中原因是放在函 数中时,执行函数时才建立对象,并且对象初始化时间确定的,即第一次执行该函数时另外,该函数不能声明为内联,如果内联可能造成程序的静态对象拷贝超过 一个
限制对象个数:建立一个基类,构造函数中计数加一,若超过最大值则抛出异常;析构函数中计数减一
编程点滴:
将模板类的定义和实现放在一个文件中,否则将造成引用未定义错误(血的教训);
静态数据成员需要先声明再初始化;
用常量值作初始化的有序类型的const静态数据成员是一个常量表达式(可以作为数组定义的维数);
构造函数中抛出异常,将导致静态数组成员重新初始化
条款27:要求或禁止在堆中产生对象
在堆中的对象不一定是用new分配的对象,例如成员对象,虽然不是用new分配的但是仍然在堆中
要 求在堆中建立对象可以将析构函数声明未private,再建立一个虚拟析构函数进行对象析构此时如果建立非堆对象将导致析构函数不能通过编译当然也可 以将构造函数声明为private,但是这样将导致必须声明n个构造函数(缺省,拷贝等等)为了解决继承问题,可以将其声明为protected,解决 包容问题则只能将其声明为指针
没有办法不能判断一个对象是否在堆中,但是可以判断一个对象是否可以安全用delete删除,只需在operator new中将其指针加入一个列表,然后根据此列表进行判断
把一个指针dynamic_cast成void*类型(或const void*或volatile void*等),生成的指针将指向原指针指向对象内存的开始处但是dynamic_cast只能用于指向至少具有一个虚拟函数的对象的指针上
禁止建立堆对象可以简单的将operator new声明为private,但是仍然不能判断其是否在堆中
条款28:灵巧(smart)指针
灵巧指针的用处是可以对操作进行封装,同一用户接口
灵巧指针从模板生成,因为要与内建指针类似,必须是强类型的;模板参数确定指向对象的类型
灵巧指针的拷贝和赋值,采取的方案是当auto_ptr被拷贝和赋值时,对象所有权随之被传递此时,通过传值方式传递灵巧指针对象将导致不确定的后果,应该使用引用
记住当返回类型是基类而返回对象实际上派生类对象时,不能传递对象,应该传递引用或指针,否则将产生对象切割
测试灵巧指针是否为NULL有两种方案:一种是使用类型转换,将其转换为void*,但是这样将导致类型不安全,因为不同类型的灵巧指针之间将能够互相比较;另一种是重载operator!,这种方案只能使用!ptr这种方式检测
最好不要提供转换到内建指针的隐式类型转换操作符,直接提供内建指针将破坏灵巧指针的灵巧特性
灵巧指针的继承类到基类的类型转换的一个最佳解决方案是使用模板成员函数,这将使得内建指针所有可以转换的类型也可以在灵巧指针中进行转换但是对于间接继承的情况,必须用dynamic_cast指定其要转换的类型是直接基类还是间接基类
为了实现const灵巧指针,可以新建一个类,该类从非const灵巧指针继承这样的化,const灵巧指针能做的,非const灵巧指针也能做,从而与标准形式相同
条款29:引用计数
使用引用计数后,对象自己拥有自己,当没有人再使用它时,它自己自动销毁自己因此,引用计数是个简单的垃圾回收体系
在基类中调用delete this将导致派生类的对象被销毁
写时拷贝:与其它对象共享一个值直到写操作时才拥有自己的拷贝它是Lazy原则的特例
精彩的类层次结构:
RCObject类提供计数操作;StringValue包含指向数据的指针并继承RCObject的计数操作;RCPtr是一个灵巧指针,封装了本属于String的一些计数操作
条款30:代理类
可以用两个类来实现二维数组:Array1D是一个一维数组,而Array2D则是一个Array1D的一维数组Array1D的实例扮演的是一个在概念上不存在的一维数组,它是一个代理类
代 理类最神奇的功能是区分通过operator[]进行的是读操作还是写操作,它的思想是对于operator[]操作,返回的不是真正的对象,而是一个 proxy类,这个代理类记录了对象的信息,将它作为赋值操作的目标时,proxy类扮演的是左值,用其它方式使用它,proxy类扮演的是右值用赋值 操作符来实现左值操作,用隐式类型转换来实现右值操作
用proxy类区分operator[]作左值还是右值的局限性:要实现proxy类和原类型的无缝替代,必须申明原类型的一整套操作符;另外,使用proxy类还有隐式类型转换的所有缺点
编程点滴:不能将临时对象绑定为非const的引用的行参
条款31:让函数根据一个以上的对象来决定怎么虚拟
有 三种方式:用虚函数加RTTI,在派生类的重载虚函数中使用if-else对传进的不同类型参数执行不同的操作,这样做几乎放弃了封装,每增加一个新的类 型时,必须更新每一个基于RTTI的if-else链以处理这个新的类型,因此程序本质上是没有可维护性的;只使用虚函数,通过几次单独的虚函数调用,第 一次决定第一个对象的动态类型,第二次决定第二个对象动态类型,如此这般然而,这种方法的缺陷仍然是:每个类必须知道它的所有同胞类,增加新类时,所有 代码必须更新;模拟虚函数表,在类外建立一张模拟虚函数表,该表是类型和函数指针的映射,加入新类型是不须改动其它类代码,只需在类外增加一个处理函数即 可
条款32:在未来时态开发程序
未来时态的考虑只是简单地增加了一些额外约束:
提供完备的类,即使某些部分现在还没有被使用
将接口设计得便于常见操作并防止常见错误使得类容易正确使用而不易用错
如果没有限制不能通用化代码,那么通用化它
条款33:将非尾端类设计为抽象类
如果有一个实体类公有继承自另一个实体类,应该将两个类的继承层次改为三个类的继承层次,通过创造一个新的抽象类并将其它两个实体类都从它继承因此,设计类层次的一般规则是:非尾端类应该是抽象类在处理外来的类库,可能不得不违反这个规则
编程点滴:抽象类的派生类不能是抽象类;实现纯虚函数一般不常见,但对纯虚析构函数,它必须实现
条款34:如何在同一程序中混合使用C++和C
混合编程的指导原则:
确保C++和C编译器产生兼容的obj文件
将在两种语言下都使用的函数申明为extern C
只要可能,用C++写main()
总用delete释放new分配的内存;总用free释放malloc分配的内存
将在两种语言间传递的东西限制在用C编译的数据结构的范围内;这些结构的C++版本可以包含非虚成员函数
条款35:让自己习惯使用标准C++语言
STL 基于三个基本概念:包容器(container)选择子(iterator)和算法(algorithms)包容器是被包容的对象的封装;选择子是类 指针的对象,让你能如同使用指针操作内建类型的数组一样操作STL的包容器;算法是对包容器进行处理的函数,并使用选择子来实现
Effective C++读书笔记
条款1:尽量用const和inline而不用#define
1.为方便调试,最好使用常量
注意:常量定义一般放在头文件中,可将指针和指针所指的类型都定义成const,如const char * const authorName = Scott Meyers;
类中常量通常定义为静态成员, 而且需要先声明后定义可以在声明时或定义时赋值,也可使用借用enum的方法如enum{Num = 5};
2.#define语句造成的问题
如#define max(a, b) ((a) > (b) ? (a) : (b))
在下面情况下:
Int a= 5, b = 0;
max(++ a, b);
max(++ a, b + 10);
max内部发生些什么取决于它比较的是什么值解决方法是使用inline函数,可以使用template来产生一个函数集
条款2:尽量用而不用
用>> 和<<使得编译器自己可以根据不同的变量类型选择操作符的不同形式,而采取的语法形式相同
条款3:尽量用new和delete而不用malloc和free
使用malloc和free的时候不会自己调用构造函数和析构函数,因此如果对象自己分配了内存的话,那么这些内存会全部丢失另外,将new和malloc混用会导致不可预测的后果
条款4:尽量使用C++风格的注释
C++的注释可以在注释里还有注释,所以注释掉一个代码块不用删除这段代码的注释C则不行
条款5:对应的new和delete要采用相同的形式
调 用new时用了[],调用delete时也要用 []如果调用new时没有用[],那调用delete时也不要用[]对于typedef来说,用new创建了一个typedef定义的类型的对象后, delete时必须根据typedef定义的类型来删除因此,为了避免混乱,最好杜绝数组类型用typedef
条款6:析构函数里对指针成员调用delete
删除空指针是安全的,因此在析构函数里可以简单的delete类的指针成员,而不用担心他们是否被new过
条款7:预先准备好内存不足的情况
1.用try-cache来捕获抛出的异常
2. 当内存分配请求不能满足时,调用预先指定的一个出错处理函数这个方法基于一个常规,即当operator new不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数一般称之为new-handler函数还可以创建一个混合风格的基类这种基 类允许子类继承它某一特定的功能(即函数)
条款8:写operator new和operator delete时要遵循常规
内存分配程序支持new-handler函数并正确地处理了零内存请求,并且内存释放程序处理了空指针此外还必须要有正确的返回值
条款9:避免隐藏标准形式的new
在 类里定义了一个称为operator new的函数后,会不经意地阻止了对标准new的访问(到底如何隐藏的???)一个办法是在类里写一个支持标准new调用方式的operator new,它和标准new做同样的事,这可以用一个高效的内联函数来封装实现另一种方法是为每一个增加到operator new的参数提供缺省值
条款10:如果写了operator new就要同时写operator delete
operator new和operator delete需要同时工作,如果写了operator new,就一定要写operator delete对于为大量的小对象分配内存的情况,可以考虑使用内存池,以牺牲灵活性来换取高效率
条款11:为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
如果没有自定已拷贝构造函数和赋值操作符,C++会生成并调用缺省的拷贝构造函数和赋值操作符,它们对对象里的指针进行逐位拷贝,这会导致内存泄漏和指针重复删除因此,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值运算符函数
条款12:尽量使用初始化而不要在构造函数里赋值
尽量使用成员初始化列表,一方面对于成员来说只需承担一次拷贝构造函数的代价,而非构造函数里赋值时的一次(缺省)构造函数和一次赋值函数的代价;另一方面const和引用成员只能被初始化而不能被赋值
条款13:初始化列表中的成员列出的顺序和它们在类中声明的顺序相同
类的成员是按照它们在类里被声明的顺序进行初始化的,和它们在成员初始化列表中列出的顺序没有关系
条款14:确定基类有虚析构函数
通过基类的指针去删除派生类的对象,而基类有没有虚析构函数时,结果将是不可确定的因此必须将基类的析构函数声明为virtual但是,无故的声明虚析构函数和永远不去声明一样是错误的,声明虚函数将影响效率
条款15:让operator=返回*this的引用
当 定义自己的赋值运算符时,必须返回赋值运算符左边参数的引用,*this如果不这样做,就会导致不能连续赋值,或导致调用时的隐式类型转换不能进行(隐 式类型转换时要用到临时对象,而临时对象是const的),或两种情况同时发生对于没有声明相应参数为const的函数来说,传递一个const对象是 非法的
条款16:在operator=中对所有数据成员赋值
当类里增加新的数据成员时,要记住更新赋值运算符函数对基类的私有成员赋值时,可以显示调用基类的operator=函数派生类的拷贝构造函数中必须调用基类的拷贝构造函数而不是缺省构造函数,否则基类的数据成员将不能初始化
条款17:在operator=中检查给自己赋值的情况
显 示的自己给自己赋值不常见,但是程序中可能存在隐式的自我赋值:一个对象的两个不同名字(引用)互相赋值首先,如果检查到自己给自己赋值就立即返回,可 以节省大量的工作;其次,一个赋值运算符必须首先释放掉一个对象的资源,然后根据新值分配新的资源,在自己给自己的情况下,释放旧的资源将是灾难性的
条款18:争取使类的接口完整并且最小
必要的函数是拷贝构造函数,赋值运算符函数,然后在此基础上选择必要的方便的函数功能进行添加
条款19:分清成员函数,非成员函数和友元函数
虚函数必须是成员函数如果f必须是虚函数,就让它称为类c的成员函数
ioerator>>和operator<<决不能是成员函数如果f是operator>>或operator<<,让f称为非成员函数如果f还需要 访问c的非公有成员,让f称为c的友元
其它情况下都声明为成员函数如果以上情况都不是,让f称为c的成员函数
Result = onehalf * 2;能通过编译的原因:调用重载*操作符的成员函数,对参数2进行隐式类型转换
Result = 2 * onehalf;不能通过编译的原因:不能对成员函数所在对象(即成员函数中this指针指向的对象)进行转换
条款20:避免public接口出现数据成员
访问一致性,public接口里都是函数
精确的访问控制,可以精确设置数据成员的读写权限
功能分离,可以用一段计算来取代一个数据成员举例:计算汽车行驶的平均速度
条款21:尽量使用const
如果const出现在*号左边,指针指向的数据为常量;如果const出现在*号右边,则指针本身为常量;如果const在两边都出现,二者都是常量
将operator的返回结果声明为const,以防止对返回结果赋值,这样不合常规
c+ +中的const:成员函数不修改对象中的任何数据成员时,即不修改对象中的任何一个比特时,这个成员函数才是const的造成的问题是可以修改指针指 向的值,而且不能修改对象中的一些必要修改的值解决方案是将必要修改的成员运用mutable关键字另一种方法是使用const_cast初始化一个 局部变量指针,使之指向this所指的同一个对象来间接实现还有一种有用又安全的方法:在知道参数不会在函数内部被修改的情况下,将一个const对象 传递到一个取非const参数的函数中
条款22:尽量用传引用而不用传值
传值将导致昂贵的对象开销,而传引用则非常高效
传引用避免了切割问题,即当一个派生类的对象作为基类对象被传递是,派生类的对象的作为派生类所具有的所有行为特性会被切割掉,从而变成了一个简单的基类对象
条款23:必须返回一个对象时不要试图返回一个引用缩写
典型情况:操作符重载
常见的错误:
返回引用,返回的是局部对象的引用
堆中构造,使用new分配内存,但是无人负责delete的调用,从而造成内存泄漏
返回静态对象,导致调用同一函数比较时总是相等
正确的方法是直接在堆栈中创建对象并返回
条款24:在函数重载和设定参数缺省值间慎重选择
如果可以选择一个合适的缺省参数,否则就使用函数重载
有一些情况必须使用重载:函数的结果取决于传入参数的个数;需要完成一项特殊的任务
条款25:避免对指针和数字类型重载
对于f(0):0代表int还是null编译器认为是int,这和人们的想象不一样解决办法是使用成员模板,构造一个可以产生null指针对象的类最重要的是,只要有可能,就要避免对一个数字和一个指针类型重载
条款26:当心潜在二义性
情形1:可以通过构造函数和转换运算符产生另一个类的对象,这时编译器将拒绝对其中的一种方法进行选择
情形2:f(int);f(char);对于f(double)时产生二义
情形3:多继承时,两个基类有同名的成员此时必须指定基类方可调用,而不考虑访问控制权限和返回值
条款27:如果不想使用隐式生成的函数就要显式地禁止它
方法是声明该函数,并使之为private显式地声明一个成员函数,就防止了编译器去自动生成它的版本;使函数为private,就防止了别人去调用它为了防止成员函数和友元函数的调用,只声明而不定义这个函数
条款28:划分全局名字空间
使用名字空间,以防止不同库的名字冲突对于不支持名字空间的编译器,可以使用struct来模拟名字空间,但是此时运算符只能通过函数调用来使用
条款29:避免返回内部数据的句柄
对于const成员函数来说,返回句柄可能会破坏数据抽象如果返回的不是指向const数据的句柄,数据可能被修改对非const成员函数来说,返回句柄会带来麻烦,特别是涉及到临时对象时句柄就象指针一样,可以是悬浮的
条款30:避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级比这个函数要低
如果获得了私有或保护成员(包括成员函数)的地址(指针或引用),那么就可以象对待公有成员一样进行访问如果不得不返回其引用或指针,可以通过返回指向const对象的指针或引用来达到两全其美的效果
条款31:千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用
如 果返回局部对象的引用,那个局部对象其实已经在函数调用者使用它之前就被销毁了而返回废弃指针的问题是必须要有人负责调用delete,而且对于 product=one*two*three*four;的情况,将产生内存泄漏因此,写一个返回废弃指针的函数无异于坐等内存泄漏的来临
条款32:尽可能地推迟变量的定义
不 仅要强变量的定义推迟到必须使用它的时候,还要尽量推迟到可以为它提供一个初始化参数位置这样做,不仅可以避免对不必要的对象进行构造和析构,还可以避 免无意义的对缺省构造函数的调用而且,在对变量初始化的场合下,变量本身的用途不言自明,在这里定义变量有益于表明变量的含义
条款33:明智使用内联
内联函数的本质是将每个函数调用以它的代码体来替换
大多数编译器拒绝复杂的内联函数(例如,包含循环和递归的函数);还有,即使是最简单的虚函数调用,编译器的内联处理程序对它也爱莫能助
若编译器不进行内联,则将内联函数当作一般的外联函数来处理这称为被外联的内联
找出重要的函数,将它内联同时要注意代码膨胀带来的问题,并监视编译器的警告信息,看看是否有内联函数没有被编译器内联
条款34:将文件间的编译依赖性降至最低
如果可以使用对象的引用和指针,就要避免使用对象本身定义某个类型的引用和指针只会涉及到这个类型的声明,定义此类型的对象则需要类型定义的参与
尽可能使用类的声明,而不使用类的定义因为在声明一个函数时,如果用到某个类,是绝对不需要这个类的定义的,即使函数是通过传值来传递和返回这个类
不要在头文件中再包含其它头文件,除非缺少了它们就不能编译相反,要一个一个地声明所需要的类,让使用这个头文件的用户自己去包含其它的头文件
最后一点,句柄类和协议类都不大会使用类联函数使用任何内联函数时都要访问实现细节,而设计句柄类和协议类的初衷正是为了避免这种情况
条款35:使公有继承体现是一个的含义
如果类D从类B公有继承时,类型D的每一个对象也是类型B的一个对象,但反之不成立任何可以使用类型B的对象的地方,类型D的对象也可以使用
特别注意一般理解中的是一个,比如企鹅是鸟,并不严密如果涉及到飞这个动作,二者之间不适合使用公有继承
条款36:区分接口继承课实现继承
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口也可以为纯虚函数提供一种缺省实现
声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现
声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现
条款37:绝不要重新定义继承而来的非虚函数
如果重新定义继承而来的非虚函数,将导致对象对函数的调用结果由指向其的指针决定,而不是由对象本身的类型来决定另外,也是类的设计产生矛盾,因为公有继承的含义是是一个,改变继承而来的方法显然是不合理的
条款38:绝不要重新定义继承而来的缺省参数值
虚函数动态绑定,而缺省参数是静态绑定因此重新定义继承而来的缺省参数值可能造成调用的是定义在派生类,但使用了基类中缺省参数值的虚函数
条款39:避免向下转换继承层次
采用向下转换时,将不利于对代码进行维护,可以采用虚函数的方法来解决
不得不进行向下转换时,采用安全的向下转换:dynamic_cast运算符dynamic_cast运算符先尝试转换,若转换成功就返回新类型的合法指针,若失败则返回空指针
条款40:通过分层来体现有一个或用来实现
公有继承的含义是是一个对应地,分层的含义是有一个或用来实现例如,要实现set类,因为list中可以包含重复元素,因此set不是一个listset可以用list来实现,即在set中包含一个list
条款41:区分继承和模板
当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类
当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类
条款42:明智地使用私有继承
关于私有继承的两个规则:和公有继承相反,如果两个类之间的继承关系为私有,编译器一般 不会将派生类对象转换为基类对象;从私有基类继承而来的成员都称为了派生类的私有成员,即使它们在基类中是保护或公有成员
私有继承意味这用来实现,但是应尽可能使用分层,必须时才使用私有继承
条款43:明智地使用多继承
多 继承后果:二义性,如果一个派生类从多个基类继承了一个成员名,所有对这个名字的访问都是二义的,你必须明确说出你所指的是哪个成员这可能导致虚函数的 失效,并且不能对多继承而来的几个相同名称虚函数同时进行重定义钻石型继承,此时向虚基类传递构造函数参数时要在继承结构中最底层派生类的成员初始化列 表中指定同时还要仔细想想虚函数的优先度
然而在适当时候还是可以使用多继承,例如将接口的公有继承和实现的私有继承结合起来的情况
以增加中间类的代价来消除多继承有时侯是值得的一般应该避免使用多继承以减少继承结构的复杂性
条款44:说你想说的,理解你所说的
理解不同的面向对象构件在C++中的含义:
· 共同的基类意味着共同的特性
· 公有继承意味着 是一个
· 私有继承意味着 用来实现
· 分层意味着 有一个 或 用来实现
下面的对应关系只适用于公有继承的情况:
· 纯虚函数意味着仅仅继承函数的接口
· 简单虚函数意味着继承函数的接口加上一个缺省实现
· 非虚函数意味着继承函数的接口加上一个强制实现
条款45:弄清C++在幕后为你所写所调用的函数
如 果没有声明下列函数,编译器会声明它自己的版本:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符和一个缺省构造函数对于拷贝构造函数 和赋值运算符,官方的规则是:缺省拷贝构造函数(赋值运算符)对类的非静态数据成员进行以成员为单位的逐一拷贝构造(赋值)
特别要注意由于编译器自动生成的函数造成的编译错误
条款46:宁可编译和链接是出错,也不要运行时出错
通常,对设计做一点小小的改动,就可以在编译期间消除可能产生的运行时错误这常常涉及到在程序中增加新的数据类型例如对于需要类型检查的Month,可以将其设为一个Month类:构造函数私有,产生对象使用静态成员函数,每个Month对象为const
条款47:确保非局部静态对象在使用前被初始化
如果在某个被编译单元中,一个对象的初始化要依赖于另一个被编译单元中的另一个对象的值,并且这第二个对象本身也需要初始化,就有可能造成混乱
虽 然关于 非局部 静态对象什么时候被初始化,C++几乎没有做过说明;但对于函数中的静态对象(即,局部 静态对象)什么时候被初始化,C++却明确指出:它们在函数调用过程中初次碰到对象的定义时被初始化如果不对非局部静态对象直接访问,而用返回局部静态 对象引用的函数调用来代替,就能保证从函数得到的引用指向的是被初始化了的对象
条款48:重视编译器警告
重视编译器产生的每一条警告信息在忽略一个警告之前,一定要准确理解它想告诉你的含义
条款49:熟悉标准库
对 于C++头文件,最大的挑战是把字符串头文件理清楚:是旧的C头文件,对应的是基于char*的字符串处理函数; 是包装了std的C++头文件,对应的是新的string类(看下文);是对应于旧C头文件 的std版本如果能掌握这些,其余的也就容易了
库中的一切都是模板
条款50:提高对C++的认识
C++的设计目标:和C兼容,效率,和传统开发工具及环境的兼容性,解决真实问题的可应用性
参考C++标准,理解C++的设计过程
下面转载自http://blog.csdn.net/dotscylla/article/details/5384764
pointers(指针)、references(引用)、casts(类型转换)、arrays(数组)、constructors(构造)--再没有比这些更基础的议题了。几乎最简单的C++程序也会用到其中的大部分特性
一、条款1--指针与引用的区别
1.首先要认识到任何情况下都不能使用指向空值的引用,一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用
2.应该远离这样的代码:char* pc = NULL; char& rc = *pc;
3.因为引用肯定会指向一个对象,在C++里,引用应该被初始化
4.不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针要高。因为使用引用之前不需要测试它的合法性
5.指针可以被重新赋值以指向别一个不同的对象,但是引用则总是指向在初始化时被指定的对象,以后不能改变
6.总得来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(可将指针设置为空),二是你需要能够在不同的时刻指向不同的对象(可以改变指针的指向)。如果况是指向一个对象,并且一旦指向一个对象后不会改变指向,你应该使用引用
6.当你重载某个操作符时,你应该使用引用。最普通的例子就是操作符[]。这个操作符典型用法是返回一个目标对象,其能被赋值
二、条款2--尽量使用C++风格的类型转换
1.C++中引进四个新的类型转换操作符克服了C风格类型转换的缺点(1-过于粗鲁,能允许你在任何类型之间进行转换;2-在程序语句中难以识别),这四个操作符是,static_cast/const_cast/dynamic_cast/reinterpret_cast
2.static_cast在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上限制。如你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或把double类型转换成指针类型
3.const_cast用于类型转换掉表达式的const或volatileness属性
4.dynamic_cast用于安全地沿着类的继承关系向下进行类型转换。失败的话将返回空指针(当对指针类型进行转换时)或抛出异常(当对引用进行类型转换时)。dynamic_casts在帮且你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上(见条款24),也不能用它来转换掉constness
5.reinterpret_cast,这个操作符的类型转换,其转换结果几乎都是执行期定义。因此,使用reinterpret_cast的代码很难移植。最普通的用途就是在函数指针类型之间进行转换
三、条款3--不要对数组使用多态
1.语言规范中说通过一个基类指针来删除一个含有派生类对象的数组,其结果将是不确定的
2.多态和指针算法不能混合在一起来使用,所以数组与多态也不能用在一起
四、条款4--避免无用的缺省构造函数
1.在一个完美世界里,无需任何数据即可建立对象的类可以包含缺省构造函数,而需要数据来建立对象的类则不能包含缺省构造函数,唉!可是我们的现实世界是不完美的,所以我们必须考虑更多的因素。特别是如果一个类没有缺省构造函数,就会存在一些使用上的限制
第一种问题:建立对象数组时。一般来说,没有一种办法建立对象数组时给构造函数传递参数。不过还是有三种方法可以避开这个限制:
一、使用非堆数组(non-heap arrays)。缺点:不能用在堆数组(heap arrays)的定义上
二、利用指针数组来代替一个对象数组。缺点:(1)容易发生内存泄漏 (2)增加了内存分配量
三、为数组分配raw memory,使用placement new方法。缺点:不熟悉,使用不方便
第二种问题:无法在许多其于模板(template-based)的容器里使用。因为实例化一个模板时,模板的类型参数应该提供一个默认的缺省构造函数,这是一个常见的要求
第三种问题:设计虚基类时所面临的要提供缺省构造函数还是不提供的两难决策。不提供缺省构造函数的虚基类,很难与其合作。因为几乎所有的派生类在实例化时都必须给虚基类构造函数提供参数。这就要求所有由此虚基类继承下来的派生类(无论有多远)都必须知道并理解提供给虚基类构造函数的参数的意义。
2.提供无意义的缺省构造函数也会影响类的工作效率。如果成员函数必须测试所有的部分是否都被正确的初始化,那么这些函数的调用者就得为此付出更多的时间,而且还得付出更多的代码,它们也得在测试失败的地方放置代码来处理错误。如果一个类的构造函数能够确保所有的部分被正确的初始化,所有这些弊病都能够避免。缺省构造函数一般不会提供这种保证,所以在它们可能使类变的没有意义时,尽量去避免使用它们。
运算符重载,允许给予你的自定义类型有着和C++内建类型完全相样的语法,更有甚者,它们允许你将强大的能量注入到运算符背后的函数体中。然而它也是很难驾驭的,单参数的构造函数和隐式类型转换尤其棘手,因为它们会被调用在没有任何的源代码显示了这样的调用的地方
五、条款5--谨慎定义类型转换函数
1.C++编译器能够在两种数据类型之间进行隐式转换(implicit conversions),它继承了C语言的转换方法。这种可怕的转换可能会导致数据的丢失
2.1所提到的是语言本身的特性,你无能为力。不过当你定义自己的类型时,就有更多的控制力,因为你能选择是否提供函数让编译器进行隐式类型转换
3.有两种函数允许编译器进行这些转换:单参数构造函数(single-argument-constructors)和隐式类型转换运算符
4.单参数构造函数:只定义了一个参数的函数或虽定义了多个参数但第一个参数以后的所有参数都有缺省值
5.隐式类型转换操作符只是一个样子奇怪的成员函数:operator关键字,其后跟一个类型符号。你不用定义函数的返回类型,因为返回类型就是这个函数的名字
6.说了上面几条,根本问题是当你在不需要使用转换函数时,这些函数却会被调用运行。它表明了隐式类型转换的缺点:它们的存在将导致错误的发生
7.解决6的办法是,用不使用语法关键字的等同的函数来替代转换运算符(显式调用转换成员函数),虽然这种显式转换函数的使用不方便,但是函数被悄悄调用的情况不再会发生
8.通过单参数构造函数进行隐式类型转换更难消除 解决办法:(1)explicit关键字 (2)使用自定义类型来代替构造函数的单参数
六、条款6--自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别
1.句法问题:重载函数间的区别决定于它们的参数类型上的差异,但是不论是increment或decrement的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型的参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数
class UPInt
{
public:
UPInt& operator++(); // ++前缀
const UPInt operator++(int); // ++后缀
UPInt& operator--() // --前缀
const UPInt operator--(int); // --后缀
UPInt& operator +=(int) // +=操作符,UPInts
};// 注意返回值类型,前缀形式返回一个引用,后缀形式返回一个const类型
2.从你开始做C程序员那天开始,你就记住increment的前缀形式有时叫做“增加然后取回”,后缀形式叫做“取回然后增加”
UPInt& UPInt::operator++()
{
*this += 1; // 增加
return *this; // 取回
}
const UPInt UPInt::operator++()
{
UPInt oldValue = *this; // 取回值
++(*this); // 增加
return oldValue;
}
3.后缀操作符函数没有使用它的参数。它的参数只是用来区分前缀和后缀函数调用。如果没有在函数里使用参数,许多编译器会显示警告信息,很令人讨厌。为了避免这些警告信息,一种经常使用的方法是省略掉你不想使用的参数名称
七、条款7--不要重载"&&","||",或","
1.与C一样,C++使用布尔表达式短路求值法(short-circuit evaluation)。这表示一旦确定了布尔表达式的真假值,即使还有部分表达式没有被测试,布尔表达式也停止运算
2.如果你重载&&或||,就没有办法提供给程序员他们所期望和使用的行为特性,因为你以函数调用法替代了短路求值法,极大地改变了游戏规则
3.C++语言规范没有定义函数参数的计算顺序
4.C++的逗号表达式规则:一个包含逗号的表达式首先计算逗号左边的表达式,然后计算逗号右边的表达式;整个表达式的结果是逗号右边表达式的值
5.提到逗号表达式的规则,主要是要重载,你需要模仿这个行为特性。不幸的是你无法模仿
6.你不能重载的操作符:. .* :: ?: new delete sizeof typeid
7.你可以重载的操作符:operator new operator delete operator new[] operator delete[]
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () []
8.能重载操作这些操作符不是去重载的理由。操作符重载的目的是使程序更容易阅读,书写和理角,而不是用你的知识去迷惑其他人。如果没有一个好理由重载操作符,就不要重载
八、条款8--理解各种不同含义的new和delete
1.new操作符(new operator)和new操作(operator new)
2.new操作符完成的功能分两部分,第一部分是分配足够的内存以便容纳所需类型的对象。第二部分是它调用构造函数初始化内存中的对象。new操作符总是做这两件事情,你不能以任何方式改变它的行为。你所能改变的是如何为对象分配内存。
3.new操作符调用一个函数来完成必需的内存分配,你能够重新或重载这个函数来改变它的行为
4.new操作符为分配内存所调用函数的名字是operator new
5.operator new声明:
void* operator new( size_t size );
返回值类型是void*,因为这个函数返回一个未经处理(raw)的指针,未初始化内存。你能增加额外的参数重载函数operator new,但是第一个参数类型必须是size_t。
像malloc一样,operator new的职责是分配内存,它对构造函数一无所知
6.有时你有一些已经被分配但是尚未处理的(raw)内存,你需要在这些内存中构造一个对象,你可以使用一个特殊的operator new,它被称为placement new
7.为了使用placement new,你必须使用语句#include <new>
8.如果你想在堆上建立一个对象,应该用new操作符。它既分配内存又为对象调用构造函数。如果你仅仅想分配内存,就应该调用operator new函数,它不会调用构造函数。如果你想定制自己的堆对象被建立时的内存分配过程,你应该写你自己的operator new函数,然后使用new操作符,new操作符会调用你定制的operator new。如果你想在一块已经获得指针的内存里建立一个对象,应该用placement new
9.为了避免内存泄漏,每个动态内存分配必须与一个等同相反的deallocation对应。函数operator delete与delete操作符的关系与operator new与new操作符的关系一样。
异常
如果你需要一个方法,能够通知不可被忽略的异常状态,并且搜索栈空间以便找到异常处理代码时,你还得确保局部对象的析构函数必须被调用,这时你就需要使用C++的异常处理
九、条款9--使用析构函数防止资源泄漏
1.ALA* pa = readALA( dataResource );
pa->processAdoption();
delete pa;
如果processAdoption()抛出异常,processAdoption()没有捕获异常,所以异常将传递给processAdoption的调用者。delete pa会被跳过,造成资源泄漏
堵塞很容易:
ALA* pa = readALA( dataResource );
try {
pa->processAdoption();
}
catch(...){
delete pa;
throw;
}
delete pa;
但这样的双份清除代码让人心烦且难于维护。应该用pointer-like对象(smart pointer)
auto_ptr<ALA> pa(readALA( dataResource ) );
pa->processAdoption();
2.资源应该被封装在一个对象里,遵循这个规则,你通常就能避免在存在异常环境里发生资源泄漏
十、条款10--在构造函数中防止资源泄漏
1.C++仅仅能删除被完全构造的对象(full constructed objects),只有一个对象的构造函数完全运行完毕,这个对象才被完全地构造。C++拒绝为没有完成构造操作的对象调用析构函数是有一些原因的:如果为没有完成构造操作的对象调用析构函数,析构函数如何去做?仅有的办法是在每个对象里加入一些字节来指示构造函数执行了多少步?然后让析构函数检测这些字节并判断执行哪些操作。这样的记录会减慢析构函数的运行速度,并使得对象的尺寸变大。C++避免了这种开销,但是代价是不能自动删除被部分构造的对象
2.经常用的方法是捕获所有的异常,然后执行一些清除代码,最后再重新抛出异常让它继续传递
3.SomeClass* const mptr;类似于这们的成员指针必须通过构造函数的成员初始化列表来初始化,因为再也没有其他地方可以给const指针赋值
4.如果你用对应的auto_ptr对象替代指针成员变量,就可以防止构造函数存在异常时发生资源泄露,你也不用手工在析构函数中释放资源,并且你还能象以前使用非const指针一样使用const指针,给其赋值
十一、条款11--禁止异常信息(exceptions)传递到析构函数外
1.在两种情况下会调用析构函数,一是在正常情况下删除一个对象,例如对象超出了作用域或被显式地delete;二是异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统删除一个对象
2.在上述两种情况下,调用析构函数时异常可能处于激活状态也可能处于没有激活状态。遗憾的是没有办法在析构函数内部区分出这两种情况。因此写析构函数时你必须保守地假设有异常被激活。因为如果在一个异常被激活的同时,析构函数也抛出异常,并导致程序控制权转移到析构函数外,C++将调用terminate函数。这个函数的作用正如其名字所表示的:它终止你的程序的运行,而且是立即终止,甚至连局部对象都没有释放
3.不允许异常传递到析构函数外面的第二个原因:如果一个异常被析构函数抛出而没有在函数内部捕获住,那么析构函数就不会完全运行(它会停在抛出异常的那个地方上)
十二、条款12--理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异
1.第一个差异:调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方
2.C++规范要求被做为异常抛出的对象必须被复制(防止超出作用域被释放),即使被抛出的对象不会被释放(如static的),也会进行拷贝操作。这表示,即使通过引用来捕获异常,也不能在cacth块中修改原对象,仅仅修改的是原对象的拷贝
3.第二个差异:抛出异常运行速度比传递参数要慢
4.在函数调用中不允许传递一个临时对象到一个非const引用类型的参数里,但是异常中却被允许
5.当抛出一个异常时,系统构造的(以后会析构掉)被抛出对象的拷贝数比以相同对象做为参数传递给函数时构造的拷贝数要多一个
6.通过指针抛出异常与通过指针传递参数是相同的。不论哪种方法都是一个指针的拷贝被传递。但,你不能认为抛出的指针是一个指向局部对象的指针,因为当异常离开局部变量的生存空间时,该局部变量已经被释放。catch子句将获得一个指向已经不存在的对象的指针。这种行为在设计时应该予以避免(注:也就是说,必须是全局的或堆中的)
7.第三个差异:在函数调用者或抛出异常者与被调用者或异常捕获者之间的类型匹配的过程不同
如 double sqrt( double ); // from <cmath> or <math.h>
我们能这样计算一个整数的平方根:
int i;
double sqrt0fi = sqrt( i );
毫无疑问,C++允许进行从int到double的隐式类型转换。一般来说,catch子句匹配异常类型时不会进行这样的转换,如:
void f( int value )
{
try{
if( someFunction() ) {
throw value;
...
}
}
catch( double d ) {
...
}
}
在try块中抛出的int异常不会被处理double异常的catch子句捕获。该子句只能捕获类型真真正正为double的异常,不进行类型转换
8.catch子句中进行异常匹配时可以进行两种类型转换:第一种是继承类与基类间的转换;第二种允许从一个类型化指针(typed pointer)转变成无类型指针(untyped pointer),所以带有const void* 指针的catch子句能捕获任何类型的指针类型异常
9.最后一点差异:catch子句匹配顺序总是取决于它们在程序中出现的顺序。与这种行为相反,当你调用一个虚拟函数时,被调用的函数位于与发出函数调用的动态类型最相近的类里。你可以这样说虚拟函数采用最优适合法,而异常处理采用最先适合法。如果一个处理派生类异常的catch子句位于处理基类异常的catch子句后面,编译器会发出警告(这样的代码在C++里通常是不合法的)。不过你最好做好预先防范:不要把处理基类异常的catch子句放在处理派生类异常的catch子句的前面
十三、条款13--通过引用(reference)捕获异常
1.通过指针方式捕获异常是最高效的(能够不拷贝对象)。但如果catch子句接收到的指针,不是全局或静态的或堆的,那简直非常糟糕。就算是建立的一个堆对象,但catch子句无法判断是否应该删除该指针?如果是在堆中建立的,那必须删除,否则会资源泄漏。如果不是在堆中建立的异常对象,他们绝对不能删除它,否则程序的行为将不可预测。这是不可能知道的。而且通过指针捕获异常也不符合C++语言本身的规范,所以最好避开它
2.通过值捕获异常时系统将对异常对象拷贝两次(一次建立临时对象,一次拷贝--条款12),而且它会产生slicing problem,即派生类的异常对象被做为基类异常对象捕获时,那它的派生类的行为就被切掉了(sliced off),这样的sliced对象实际上是一个基类对象(当一个对象通过传值方式传递给函数,也会发生一样的情况)
3.通过引用捕获异常,就不会为删除异常对象而烦恼;能够避开slicing异常对象;能够捕获标准异常类型;减少对象需要被拷贝的数目
十四、条款14--审慎使用异常规格(exception specifiations)
1.异常规格明确的描述了一个函数可以抛出什么样的异常。但是它不只是一个有趣的注释,编译器在编译时有时能够检测到异常规格的不一致。而且如果一个函数抛出一个不在异常规格范围里的异常,系统在运行时能够检测出这个错误,然后一个特殊的函数unexcepted将被调用,此函数的缺省行为是调用函数terminate,而terminate的缺省行为是调用函数abort
2.避免调用unexcepted的第一个方法:避免在带有类型参数的模板内使用异常规格
3.避免调用unexcepted的第二个方法:如果在一个函数内调用其他没有异常规格的函数时应该去除这个函数的异常规格
4.避免调用unexcepted的第三个方法:处理系统本身抛出的异常
十五、条款15--了解异常处理的系统开销
1.在理论上,异常是C++的一部分,C++编译器必须支持异常
2.为了使你的异常开销最小化,只要可能就尽量彩不支持异常的方法编译程序,把使用try块和异常规格限制在你确实需要它们的地方,并且只有在确为异常的情况下才抛出异常
效率
在用C++写出高效地程序之前,必须认识到C++本身绝对与你所遇到的任何性能上的总是无关。如果想写出一个高效的C++程序,你必须首先能写出一个高效的算法
十六、条款16--牢记80-20准则(80-20 rule)
1.80-20准备说的是大约定俗成20%的代码使用了80%的程序资源;大约20%的代码耗用了大约80%的运行时间;大约20%的代码使用了80%的内存;大约20%的代码执行80%的磁盘访问;80%的维护投入于大约20%的代码上,也就是说,软件的整体性能取决于代码组成中的一小部分
2.用profiler程序识别出令人讨厌的程序的20%部分。让它去直接的测试你感兴趣的资源
十七、条款17--考虑使用lazy evaluation(懒惰计算法)
1.从效率的观点来看,最佳的计算就是根本不计算。关键是要懒惰
2.当你使用了lazy evaluation后,采用此种方法的类将推迟计算工作直到系统需要这些计算的结果。如果不需要结果,将不用进行计算
3.引用计数
4.区别对待读取和写入:残酷的事实是我们如何判断是读取操作还是写入操作,通过使用lazy evluation和条款M30中讲述的proxy class,我们可以推迟做出是读操作还是写操作的决定
5.Lazy Fetching(懒惰提取)
6.Lazy Expression Evaluation(懒惰表达式的计算)
7.应用:能避免不需要的对象拷贝,通过使用operator[]区分出读操作,避免不需要的数据库读取操作,避免不需要的数字操作
十八、条款18--分期摊还期望的计算
1.这个条款的核心就是over-eager evaluation(过度热情计算法):在要求你做某些事情以前就完成它们
2.隐藏在over-eager evaluation后面的思想是如果你认为一个计算需要频繁进行,你就可以设计一个数据结构高效地处理这些计算需求,这样可以降低每次计算需求时的开销
3.采用over-eager最简单的办法就是caching(缓存)那些已经被计算出来而以后还有可能需要的值
4.caching是一种分摊期望的计算开销的方法,prefetching(预提取)是另一种方法。你可以把prefetch想像成购买大批商品而获得的折扣,以空间换效率
十九、条款19--理解临时对象的来源
1.在C++中,真正的临时对象是看不见的,它们不出现在源代码中。建立一个没有命名的非堆对象会产生临时对象。这种未命名的对象通常在两种条件下产生:为了使函数调用而进行隐式类型转换和函数返回对象时
2.仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些类型转换。当传递一个非常量引用(reference-to-non-const)参数对象,就不会发生
3.C++语言禁止为非常量引用产生临时对象的原因:当程序员期望修改非临时对象时,对非常量引用进行的隐式类型转换却修改临时对象,这是不希望看到的
4.临时对象是有开销的,所以你应该尽可能地去除它们,然而更重要的是训练自己寻找可能建立临时对象的地方
二十、条款20--协助完成返回值优化
1.通过constructor argument而不是直接返回对象能让编译器消除临时对象的开销(这是编译器内部的优化)
二十一、条款21--通过重载避免隐式类型转换
1.在C++中有一条规则是每一人重载的operator必须带有一个用户自定义的参数
二十二、条款22--考虑用运算符的赋值形式(op=)取代其单独的形式(op)
1.从零开始实现operator+=和-=,而operator+和operator-则是通过调用前述的函数提供自己的功能。使用这种设计方法,只用维护operator的赋值形式就行。而且如果假设operator赋值形式在类的public接口里,这就不用让operator的单独形式成为类的友元
2.如果你不介意把所有的operator的单独形式放在全局域里,那就可以使用模板来替代单独形式的函数的编写:
template< typename T >
const T operator+( const T& lhs, const T& rhs )
{
return T(lhs) += rhs;
}
const T operator-( const T& lhs, const T& rhs )
{
return T(lhs) -= rhs;
}
3.这里指出三个效率方面的问题:一、总的来说operator的赋值形式比其单独形式效率更高,因为单独形式要返回一个新对象,从而在临时对象的构造和释放上有一些开销。operator的赋值形式把结果写到左边的参数里,因此不城要生成临时对象容纳operator的返回值;二、提供operator的赋值形式的同时也要提供其标准形式,允许类的客户端在便利与效率上做出折衷选择;三、涉及到operator单独形式的实现
二十三、条款23--考虑变更程序库
1.程序库的设计就是一个折衷的过程。理想的程序应该是短小的、快速的、强大的、灵活的、可扩展的、直观的、普遍适用的、具有良好的支持、没有使用约束、没有错误的。当然这也是不存在的。为尺寸和速度而进行优化的程序库一般不能被移值。具有大量功能的程序库不会具有直观性。没有错误的程序库在使用范围上会有限制。所以不同的设计者给这些条件赋予了不同的优先级。他们从而在设计中牺牲了不同的东西,因此一般两个提供相同功能的程序库却有着完全不同的性能特征
二十四、条款24--理解虚拟函数、多继承、虚基类和RTTI所需的代价
技巧
二十五、条款25--将构造函数和非成员函数虚拟化
1.虚拟构造函数是指能够根据输入给它的数据的不同而建立不同类型的对象
2.被派生类重定义的虚拟函数不用必须与基类的虚拟函数具有一样的返回类型。如果函数的返回类型是一个指向基类的指针(或一个引用),那么派生类的函数可以返回一个指向基类的派生类的指针(或引用)
3.就像构造函数不能真的成为虚拟函数一样,非成员函数也不能成为真正的虚拟函数。然而,既然一个函数能够构造出不同类型的新对象是可以理解的,那么同样也存在这样的非成员函数,可以根据参数的不同动态类型而其行为特性也不同
4.具有虚拟行为的非成员函数很简单。你编写一个虚拟函数来完成工作,然后再写一个非虚拟函数,它什么也不做只是调用这个虚拟函数。为了避免这个句法引起函数调用开销,你当然可以内联这个非虚拟函数
二十六、条款26--限制某个类所能产生的对象数量
1.阻止建立某个类的对象,最容易的方法就是把该类的构造函数声明在类的private域
2.任何能在全局域声明东西也能在命名空间里声明。包括类、结构、函数、变量、对象、typedef等等
3.为什么你要把对象声明为静态的呢?通常是因为你只想要对象的一个拷贝。现在再考虑内联意味着什么?从概念上讲,它意味着编译器用函数体替代对函数的每一个调用,不过非成员函数还不只这些。它还意味着internal linkage(内部链接)
4.带有内部连接的函数可能在程序内被复制(也就是说程序的目标(Object)代码可能包含一个以上的内部链接函数的代码),这种复制也包括函数内的静态对象。结果如何?如果建立一个包含局部静态对象的非成员函数,你可能会使程序的静态对象的拷贝超过一个!所以不要建立包含静态数据的非成员函数
二十七、条款27--要求或禁止在堆中产生对象
1.要求在堆中建立对象:非堆对象在定义它的地方被自动构造,在生存时间结束时自动被释放,所以只要禁止使用隐式的构造函数和析构函数,就可以实现这种限制。把这些调用变的不合法的一种最直接的方法是把构造函数和析构函数声明为private。这样做副作用太大。没有理由让两个函数都是private。最好让析构函数为private,让构造函数为publi c。你可以引进一个专用的伪析构函数,用来访问真正的析构函数。客户端调用伪析构函数释放他们建立的对象
2.异常处理体系要求所有在栈中的对象的析构函数必须申明为公有
3.另一种方法是把全部的构造函数声明为private
4.通过限制访问一个类的析构函数或它的构造函数来阻止建立非堆对象,但这种方法也禁止了继承和包容。当然也不是不能克服,把析构声明为protected可解决继承;把包容的对象改为包容的指针即可
5.判断一个对象是否在堆中:无法通过“地址对比”来确定对象是在堆上或栈上或其他地方
6.mixin("mix in")类提供某一特定的功能,并可以与其继承类提供的其他功能相兼容。这种类几乎都是抽象类。因此我们能够使用抽象混合(mixin)基类给派生类提供判断指针指向的内存是否由operator new分配的能力
7.禁止堆对象:通常对象的建立这样三种情况,对象被直接实例化;对象做为派生类的基类被实例化;对象被嵌入到其他对象内
8.禁止用户直接实例化对象很简单,因为总是调用new来建立这种对象,你能够禁止用户调用new。你不能影响new操作符的可用性,但是你能够利用new操作符总是调用operator new函数这点,来达到目的。你可以声明这个函数,而且你可以把它声明为private。但operator new是private这一点,不会对包含该类成员对象的分配产生任何影响
二十八、条款28--灵巧(smart)指针
1.灵巧指针是一种外观和行为都被设计成与内建指针相类似的对象,不过它能提供更多的功能。它们有许多应用的领域,包括资源管理和重复代码任务的自动化
2.灵巧指针从模板中生成,因为要与内建指针类似,必须是strongly typed(强类型)的;模板参数确定指向对象的类型
二十九、条款29--引用计数
三十、条款30--代理类
三十一、让函数根据一个以上的对象来决定怎么虚拟
导读
1.做一个有“线程概念”的程序员
一、条款01-视C++为一个语言联绑
1.C++是个多重范型的语言(multiparadigm programming language),一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言
2.C++并不是一个带有一组守则的一体语言,它是四个次语言(C/Object-Oriented C++/Template C++/STL)组成的联绑政府,每个次语言都有自己的规约。C++高效编程守则视状况而变化,取决于你使用C++的哪一部分
二、条款02-尽量以const/enum/inline替换#define
1.#define不能够提供任何封装性
2.有了consts/enums/inlines,我们对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要
角色。目前还不到预处理器全面引退的时候,但你应该明确地给予它更长更频繁的假期
3.对于单纯常量,最好以const对象或enums替换#define
4.对于形似函数的宏(macros),最好改用inline函数替换#defines。
三、条款03-尽可能使用const
1.char test[] = "hello";
char* p = test; // non-const pointer, non-const data
const char* p = test; // non-const pointer, const data
char const* p = test; // non const pointer, const data
char* const p = test; // const pointer, non-const data
const char* const p = test; // const pointer, const data
2.令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性
3.const成员函数,第一可以使class接口比较容易理解,这是因为得知哪个函数可以改动对象内容而哪个函数不行,很是重要的。第二,它们使“操作const对象"成为可能。
4.两个成员函数如果只是常量性(constness)不同,可以被重载
5.成员函数如果是const,有两个流行概念:bitwise constness(又称physical constness)和logical constness
6.const成员函数不可以更改对象内任何non-static成员变量
7.关键字mutable可释放掉non-static成员变量的bitwise constness约束。声明为mutable的成员变量可能总会被改变,即使在const成员函数内
8.const是个奇妙且非比寻常的东西。在指针和迭代器身上;在指针、迭代器及reference指涉的对象身上;在函数参数和返回类型身上;在local变量身上;在成员函数身上,林林总总不一而足
9.将某些东西声明为const可帮助编译器侦测出错误用法
10.编译器强制实施bitwise constness,但你编程程序时应该使用"概念上的常量性"(conceptual constness)
11.当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复[注:不要做反向调用]
四、条款04-确定对象被使用前已被初始化
1.对于无任何成员的内置类型,你必须手工完成初始化。至于内置类型以外的,初始化的责任落在构造函数身上。规则很简单:确何每一个构造函数都将对象的每一个成员初始化
2.C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前
3.所以尽量使用初始化列表(member initialization list),而不使用赋值初始化。初始化列表单只调用一次copy构造,而赋值初始化则要调用一次default构造和一次copy assignment操作符。显然使用初始化列表是比较高效的
4.C++有着十分固定的“成员初始化次序”。次序总是相同:base classes更早于其derived classes被初始化,而class的成员变量总是以其声明次序被初始化(即使它们在初始化列表中以不同的次序出现,也不会有任何影响),所以初始化列表最好是其成员变量声明顺序,以免出现晦涩错误(是指两个成员变量的初始化带有次序性。如初始化array时需要指定大小,因此代表大小的成员变量必须先有初值)
5.C++对“定义于不同的编译单元(是指产出单一目标文件的那些源码。基本上它是单一源码文件加上其所含入的头文件[#include files])内的non-local static对象”的初始化相对次序并无明确定义。可将non-local static对象般到自己的专属函数内,变为local static对象。这些函数返回一个reference指定它所含的对象。然后用户用这些函数,而不直接指涉这些对象。这也是Design Patterns里Singleton模式的一个常见手法。这个手法的基础在于:C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。但是从另一个角度看,这些函数“内含static对象“的事实使它们在多线程系统中带有不确定性。任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是:在程序的单线程启动阶段手工调用所有reference-returning函数,这个消除与初始化有关的“竞速形势”。
五、条款05-了解C++默默编写并调用哪些函数
1.class中,当C++处理过它之后,如果自己没声明任何函数,编译器就会为它声明一个default构造函数、一个copy构造函数、一个copy assignment操作符和一个析构函数。惟有当这些函数被需要(被调用),它们才会被编译器创建出来。编译器产生的析造函数是个non-virtual,除非这个class的base class自身声明有virtual析构函数
2.如果class内含reference成员或const成员,再者其base class的copy assignment操作符声明为private,编译器都会拒绝为class生成copy assignment操作符
3.class析构函数(无论是编译器生成、或用户自定的)会自动调用其non-static成员变量的析构函数
六、条款06-若不相使用编译自动生成的函数,就该明确拒绝
1.将不相使用编译自动生成的函数声明为private且故意不实现它们,这样,当客户企图拷贝这个class对象,编译器会阻挠他。如果你不慎在member函数或friend函数之内那么做,就会轮到连接器发出抱怨
2.将连接器错误移至编译期是有可能的(而且那是好事,越早侦测出错误越好),只要将copy构造函数和copy assignment操作符声明为private就可以办到,但不是在class自身,而是在一个专门为了阻止copying动作而设计的base class内。这个base class非常简单:
class Uncopyable
{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable( const Uncopyable& );
Uncopyable& operator= ( const Uncopyable& );
};
剩下的就是用class继承自Uncopyable:
class xxx:private Uncopyable
{
};
这行得通,因为尝试拷贝对象时,编译器便尝试生成一个copy构造函数和一个copy assignment操作符,而编译器会拒绝生成。
七、条款07-为多态基类声明virtual析构函数
1.如果class不含virtual函数,通常表示它并不意图被用做一个base class。当class不企图被当作base class,或者有的class被设计目的不是为了具备多态性,令其析构函数为virtual往往是个馊主意
2.polymorphic(带多态性质的) base class应该声明一个virtual析构函数。如果class带有任何的virtual函数,它就应该拥有一个virtual析构函数
八、条款08-别让异常逃离析构函数
1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或关闭程序
2.如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作
九、条款09-绝不在构造和析构过程中调用virtual函数
1.在base class构造期间,virtual函数不是virtual函数
2.derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息(runtime type infomation,例如dynamic_cast和type_id),也会把对象视为base class类型,相同道理也适用于析构函数
3.由于你无法使用virtual函数从base class向下调用,在构造期间,可以籍由“令derived class将必要的构造信息向上传递到base class构造函数”替换之而加以弥补
十、条款10-令operator=返回一个reference to *this
1.为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参
2.这个协议也适用于所有赋值相关运算,如+=、-=、*=等
十一、条款11-在operator=中处理“自我赋值”
1.所谓“别名”就是“有一个以上的方法指称(指涉)某对象”
2.确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap技术
3.确定任何一个函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确
十二、条款12-复制对象时勿忘其每一个成分
1.任何时候只要你承担起“为derived class撰写copying函数”的重大责任,必须很小心地也复制其base class成分。那些成分往往是private,所以你无法直接访问它们,你应该让derived class的copying函数调用相应的base class函数
2.copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”
3.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。
所谓资源就是,一旦用了它,将来必须还给系统。如果不这样,槽糕的事情就会发生。C++程序中最常用的资源就是动态分配内存,还包括文件描述器(file descriptors)、互斥锁(mutex locks)、图形界面中的字形和笔刷、数据库连接、以及网络sockets。不论哪种,当你不再使用时,必须将它还给系统
十三、条款13-以对象管理资源
1.为防止资源泄漏,请使用RAII(“以对象管理资源”的观念常被称为“资源取得时机便是初始化时机”[Resource Acquisition Is Initialization;RAII]),它们在构造函数中获得资源并在析构函数中释放资源
2.两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向NULL
3.auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]动作。那意味着动态分配而得的array身上使用auto_ptr或tr1::shared_ptr是个馊主意。不过可以了解boost::scoped_array和boost::shared_array classes
十四、条款14-在资源管理类中小心copying行为
1.对于非heap-based的资源而言,像auto_ptr和tr1::shared_ptr这样的智能指针往往不适合作为资源掌管者(resource handlers)
2.当一个RAII被复制,会发生什么事?常见的RAII class copying 行为:
[1]禁止复制
[2]对底层资源祭出“引用计数法”(reference-count)
[3]复制底部资源
[4]转移底部资源的拥有权
十五、条款15-在资源管理类中提供对原始资源的访问
1.APIs往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个“取得其所管理之资源”的办法
2.可以显示(如get())或隐式(operator raw_resource_class_name() const )进行转换,一般显式比较安全,但隐式对客户又比较方便
十六、条款16-成对使用new和delete时要采用相同形式
1.如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]
十七、条款17-以独立语句将newed对象置入智能指针
1.以独立语句将newed对象置入智能指针。如果不这样,一旦异常被抛出,有可能导致难以察觉得资源泄漏
不要使用
callFun( std::tr1::shared_ptr<TestClass>(new TestClass), otherFun() );
因为编译器可能选择的执行顺位是 new TestClass-->otherFun()-->tr1::shared_ptr构造函数,因为编译器觉得这样的选择可能可以生成更高效的代码,谁知道呢?
但如果otherFun()调用导致异常,会怎么样呢?
所以使用分离语句
std::tr1::shared_ptr<TestClass> pT(new TextClass );
callFun( pT, otherFun() );
这样就不会因为otherFun()可能抛出异常而导致资源泄漏。
所谓软件设计,是“令软件做出希望它做的事情”的步骤和做法,通常以颇为一般性的构想开始,最终演变成十足的细节,以允许特殊接口的开发
十八、条款18-让接口容易被正确使用,不易被误用
1.首先必须考虑客户可能做出什么样的错误
2.明智而审慎地导入新类型对预防“接口被误用”有神奇疗效
3.预防客户错误的另一个办法是,限制类型内什么事可做,什么事不可做。常见的限制是加上const
4.很少有其他性质比得上“一致性”更能导致“接口容易被正确的使用”。STL窗口的接口就十分的一致(虽然不是完美的一致),这使它们非常容易被使用
5.任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向,因为客户可能会忘记做那件事
6.“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容
7.“阻止误用”的办法包括建立新类型、限制类型上的操作、束缚对象值、以及消除客户的资源管理责任
十九、条款19-设计class犹如设计type
1.如何设计高效的class呢?首先必须了解你面对的问题
[1]新type的对象应该如何被创建和销毁?
[2]对像的初始化和对象的赋值该有什么样的差别?
[3]新type的对象如果被passed by value(以值传递),意味着什么?
[4]什么是新type的“合法值”?
[5]你的新type需要配合某个继承图系(inheritance graph)吗?
[6]你的新type需要什么样的转换?
[7]什么样的操作符和函数对此新type而言是合理的?
[8]什么样的标准函数应该驳回?
[9]谁该取用新type的成员?
[10]什么是新type的“未声明接口”(undeclared interface)?
[11]你的新type有多么一般化?
[12]你真的需要一个新type吗?
二十、条款20-宁以pass-by-reference-to-const替换pass-by-value
1.窥视C++编译器底层,你会发现,references往往以指针实现出来,因此pass by reference通常意味真正传递的是指针
2.尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)
3.2的规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较合适
二十一、条款21-必须返回对象时,别妄想返回其reference
1.绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。条款04已经为“在单线程环境中合理返回reference指向一个local stack对象”提供了一份设计实例
二十二、条款22-将成员变量声明为private
1.将成变量隐藏在函数接口的背后,可以为“所有可能的实现”提供弹性。例如这可使得成员变量被读或被写时轻松通知其他对象、可以验证class的约束条件以及函数的前提和事后状态、可以在多线程环境中执行同步控制等等。
2.protected成员变量就像public成员变量一样缺乏封装性,因为在这两种情况下,如果成员变量被改变,都会有不可预知的大量代码受到破坏。
二十三、条款23-宁以non-member、non-friend替换member函数
1.namespace可跨越多个源码文件,而class则不能
2.宁可拿non-member non-friend函数替换member函数。这样做可以增另封装性、包裹弹性(packaging flexibility)和机能扩充性
二十四、条款24-若所有参数皆需类型转换,请为此采用non-member函数
1.只有当参数被列于参数列(parameter list)内,这个参数才是隐式类型转换的合格参与者。地位相当于“被调用之成员函数所隶属的那个对象”--即this对象--的那个隐喻参数,绝不是隐式转换的合格参与者
2.如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
二十五、条款25-考虑写出一个不抛出异常的swap函数
1.能常我们不能够(不被允许)改变std命名空间内的任何东西,但可以(被允许)为标准templates(如swap)制造特化版本,使它专属于我们自己的classes。
2.当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
3.如果你提供一个member swap,也请提供一个non-member swap用来调用前者。对于classes(而非templates),也请特化std::swap
4.调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”
5.为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。
大多数情况下,适当提出你的classes(和class templates)定义以及functions(和function templates)声明,是花费最多心力的地方。一旦正确完成它们,相应的实现大多直截了当。尽管如此,还是有些东西需要小心。太快定义变量可能造成效率上的拖延;过度使用转型(casts)可能导致代码变慢又难维护,可能招来微妙难解的错误;返回对象“内部数据之号令牌(handles)”可能会破坏封装并留给客户虚吊号码牌(dangling handles);未考虑异常带来的冲击则可能导致资源泄漏和数据败坏;过度热心地inlining可能会引起代码膨胀;过度耦合(coupling)则可能导致让人不满意的冗长建置时间(build times)
二十六、条款26-尽可能延后变量定义式的出现时间
1.只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序的控制流(control flow)到达这个变量定义式时,你便得承受构造成本;当这个变量离开其作用域时,你便得承受析构成本。即使这个变量最终并未被使用,仍需耗费这些成本,所以你应该尽可能避免这种情形
2.对于for循环,做法一是定义于循环体外,做法二是定义于循环体内。除非(1)你知道做法一整体成本比做法二整体成本低,(2)你正在处理代码中效率高度敏感的部分,否则你应该使用做法二。做法一的变量名作用域比做法二大,有时那对程序的可理解性和易维护性造成冲突
3.尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率
二十七、条款27-尽量少用转型动作
1.旧式转型仍然合法,但新式转型较受欢迎。原因是:第一,它们很容易被辨识出来,因而得以简化“找出类型系统在哪个地点被破坏”的过程。第二,各转型动作的目标愈窄化,编译器愈可能诊断出错误的运用
2.单一对象(例如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以Base*指向它”时的地址和“以Derived*指向它”时的地址)
3.如果你发现你自己打算转型,那活脱是个警告信号:你可能正将局面发展至错误的方向上。如果你用的是dynamic_cast更是如此
4.除了对一般转型保持机敏与猜疑,更应该在注重效率的代码中对dynamic_cast保持机敏与猜疑(dynamic_cast的许多实现版本执行速度相当慢)
5.如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计
6.如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码内
7.宁可使用C++-style(新式)转型,不要使用旧式转型
二十八、条款28-避免返回handles指向对象内部成分
1.避免返回handles(包括references、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码版”(dangling handles)的可能性降至最低
二十九、条款29-为“异常安全”而努力是值得的
1.有个一般化的设计策略很曲型地会导致强烈保证,很值得熟悉它。这个策略被称为copy and swap。原则很简单:为你打算修改的对象(原件)做出一份副本,然后在那副本身上做一切必要的修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)
2.当异常抛出时,带有异常安全性的函数会:(1)不泄漏任何资源 (2)不允许数据败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型
3.“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义
4.函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者
三十、条款30-透彻了解inlining的里里外外
1.一个表面上看似inline的函数是否真是inline,取决于你的建置环境,主要取决于编译器。幸运的是大多数编译器提供了一个诊断级别:如果它们无法将你要求的函数inline化(如太过复杂(还有循环或递归)的函数、所有对virtual函数的调用等,inline是编译期行为),会给你一个警告信息(见条款53)
2.将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的膨胀问题最小化,使程序的速度提升机会最大化
3.不要只因为function templates出现在头文件,就将它们声明为inline
4.不要忘记80-20经验法则:平均而言一个程序往往将80%的执行时间花费在20%的代码上头。这是一个重要的法则,因为它提醒你,作为一个软件开发者,你的目标是找出这可以有效增进程序整体效率的20%代码,然后将它inline或竭尽所能的将它瘦身。但除非你选对目标,否则一切都是虚功
三十一、条款31-将文件间的编译依存关系降至最低
1.“以声明依存性”替换“定义的依存性”,那正是编译依存性最小化的本质:现实中让头文件尽可能自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。其他每一件事都源自于这个简单的设计策略:
[1]如果使用object references或object pointers可以完成任务,就不要使用objects
[2]如果能够,尽量以class声明式替换class定义式
[3]为声明式和定义式提供不同的头文件
2.支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes
3.在程序开发过程中使用Handle classes和Interface classes以求实现码有所变化时对其客户端带来最小冲击。而当它们导致速度和/或大小差异过于重大以至于classes之间的耦合相形之下不成为关键时,就以具象类(concrete classes)替换Handle classes和Interface classes。
如果你了解C++各种特性的意义,你会发现,你对OOP的看法改变了。它不再是一项用来划分语言特性的仪典,而是可以让通过它说出你对软件系统的想法。一旦你知道该通过它说些什么,转移至C++世界也就不再是可怕的高要求了
三十二、条款32-确定你的public继承塑模出is-a关系
1.is-a并非唯一存在于classes之间的关系。别两个常见的关系是has-a(有一个)和is-implemented-in-terms-of(根据某物实现出)。这些关系将在条款38和39讨论。将上述这些重要的相互关系中的任何一个误塑为is-a而造成的错误设计,在C++中并不罕见,所以你应该确定你确实了解这些个“classes相互关系”之间的差异,并知道如何在C++中最好地塑造它们
2.“public继承”意味is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象
三十三、条款33-避免遮掩继承而来的名称
1.derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此
2.为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions)
三十四、条款34-区分接口继承和实现继承
1.接口继承和实现继承不同。在public继承之下,derived classes总是继承base classes的接口
2.pure virtual函数只具体指定接口继承
3.简朴的(非纯)impure virtual函数具体指定接口继承及缺省实现继承
4.non-virtual函数具体指定接口继承以及强制实现继承。如果成员函数是个non-virtual函数,意味着它并不打算在derived classes中有不同的行为
三十五、条款35-考虑virtual函数以外的其他选择
1.籍由Non-Virtual Interface手法实现Template Method模式。这一基本设计,也就是“令客户通过public non-virtual成员函数间接调用private virtual函数”,称为non-virtual interface(NVI)手法。它是所谓Template Method设计模式(与C++ templates并无关联)的一人独特表现形式。我把这个non-virtual函数称为virtual函数的外覆器(wrapper)
2.籍由Function Pointers实现Strategy模式
3.籍由tr1::function完成Strategy模式。tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用(callable entities)--普通函数、函数对象、成员函数
4.将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员
三十六、条款36-绝不重新定义继承而来的non-virtual函数
1.non-virtual函数都是静态绑定。这意思是pB被声明为一个pointer-to-Base,通过pB调用的non-virtual函数永远是Base所定义的版本,即使pB指向一个类型为“Base派生之class”的对象
2.non-virtual函数为class建立起一个“不变性凌驾特异性”的性质。所以绝对不要重新定义继承而来的non-virtual函数
三十七、条款37-绝不重新定义继承而来的缺省参数值
1.virtual函数系动态绑定(dynamically bound),而缺省参数值却是静态绑定(statically bound)
2.对象的所谓静态类型(static type),就是它在程序中被声明时所采用的类型。而动态类型则是指“目前所指对象的类型”,也就是说,动态类型可以表现出一个对象将会有什么行为。如:
Shape* ps; // 静态类型为Shape*,无动态类型
Shape* pc = new Circle; // 静态类型为Shape*,动态类型为Circle*
Shape* pr = new Rectangle; // 静态类型为Shape*,动态类型为Rectangle*
动态类型一如其名称所示,可在程序执行过程中改变(通常是经由赋值动作)
ps = pc; // ps的动态类型如今是Circle*
ps = pr; // ps的动态类型如今是Rectangle*
3.绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数--你唯一应该覆写的东西--却是静态绑定
三十八、条款38-通过复合塑模出has-a或“根据某物实现出”
1.复合(composition)是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。在程序员之间复合这个术语有许多同义词,包括layering(分层),containment(内含),aggregation(聚合)和embedding(内嵌)
2.在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)
三十九、条款39-明智而审慎地使用private继承
1.private继承意味is-implemented-in-terms-of(根据某物实现出)。如果你让class D以private形式继承class B,你的用意是采用了class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。private继承意味只有实现部分被继承,接口部分应略去。其在软件“设计”层面上没有意义,其意义只及于软件实现层面
2.条款38才刚指出复合的意义也是这样。你如何在两者之间取舍?答案很简单:尽可能使用复合,必要时才使用private继承。何时才是必要?主要是当protected成员和/或virtual函数牵扯进来的时候。当你面对两个“并不存在is-a关系”的两个classes,其中一个需要访问另一个的protected成员,或需要重新定义其一或多个virtual函数,private极有可能成为正统设计策略
3.和复合(composition)不同,private继承可以造成EBO(empty base optimization:空白其类最优化)。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要
四十、条款40--明智而审慎地使用多重继承
1.最先要认请的一件事是,当多重继承(mutiple inheritance:MI)进入设计景框,程序有可能从一个以上的Base classes继承相同名称(如函数、typedef等等)。那会导致较多的岐义(ambiguity)机会
2.多重继承的意思是继承一个以上的Base classes,但这些base classes并不常在继承体系中又有更高级的base classes,因为那会导致要命的“钻石型MI”。
class File { ... };
class InputFile:public File { ... };
class OutputFile:public File { ... };
class IOFile:public InputFile,
public OutputFile
{ ... };
任何时候如果你有一个继承体系而其中某个base class和某个derived class之间有一条以上的相通路线,你就必须面对这样一个问题:是否打算让base class内的成员变量经由每
一条路径被复制?C++在这场辨论中并没有倾斜立场:两个方案它都支持--虽然其缺省做法是执行复制。如果那不是你想要的,你必须令那个带有此数据的class成为一个virtual
base class,当然,前提是:你得为virtual继承付出代价
class File { ... };
class InputFile:virtual public File { ... };
class OutputFile:virtual public File { ... };
class IOFile:public InputFile,
public OutputFile
{ ... };
3.非必要不使用virtual bases。如果必须使用,尽可能避免在其中放置数据。这么一来就不需要担心这些classes身上的初始化(或赋值)所带来的诡异事情了
4.多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”和“private继承某个协助实现的class”的两相结合。
C++ template机制身自是一部完整的图灵机(Turing-complete):它可以被用来计算任何可计算的值。于是导出了模板元编程(template metaprogramming),创建出“在C++编译器内执行并于编译完成时停止执行”的程序
四十一、条款41-了解隐式接口和编译器多态
1.class和template都支持接口(interfaces)和多态(polymorphism)
2.对classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期
3.对template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期
四十二、了解typename的双重意义
1.template内出现的名称如果相依于某个template参数,称之为从属名称(dependent names)。如果从属性名称在class内呈嵌套状,我们称它为嵌套从属名称(nested dependent name)。不倚赖任何template参数的名称称为非从属名称(non-dependent names )
2.一般性规则很简单:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename。当然,“typename必须作为嵌套从属类型名称的前缀词”这一规则的例外是,typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值例)中作为base class修饰符。如:
template< typename T >
class Derived: public Base<T>::Nested // base class list中
{ // 不允许"typename"
public:
explicit Derived( int x )
: Base<T>::Nested(x) // mem. init. list中
{ // 不允许"typename"
typename Base<T>::Nested temp; // 嵌套从属类型名称
... // 既不在base class list中也不在mem. init. list中,
} // 作为一个base class修饰符需加上typename.
...
};
四十三、条款43-学习处理模板化基类内的名称
1.模板全特化时,class定义式最前头的“template<>”语法象征这既不是template也不是标准class,而是个特化版的template
2.在Derived class templates内调用Base class templates的函数,无法能过编译。因为编译器并不知道它继承什么样的class--更明确地说是没办法知道Base class templates是否有这个被Derived class templates调用的函数。这也是有原因的,因为编译器知道base class templates有可能被特化,而那个特化版本可能不提供和一般性template相同的接口。因此它往往拒绝在templatized base classes(模板化基类)内寻找继承而来的名称。
3.有三种办法可以令C++“不进入templatized base classes观察”的行为失效。
[1]在base class函数调用动作之前加上"this->" // this->Fun();
[2]使用using声明式 // using Bast<T>::Fun();
[3]明白指出被调用的函数位于base class内 // Base<T>::Fun();
第三种往往是最不让人满意的一个解法,因为如果被调用的是virtual函数,这种资格修饰(explicit qualification)会关闭“virtual绑定行为”
四十四、条款44-将与参数无关的代码抽离templates
1.templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系
2.因非类型模板参数(non-type template parameters)而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数
3.因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享实现码
四十五、运用成员函数模板接受所有兼容类型
1.所谓智能指针(smart pointers)是“行为像指针”的对象,并提供指针没有的机能。真实指针做得很好的一件事是,支持隐式转换(implicit conversions)。Derived class指针可以隐式转换为base class指针,“指向non-const对象”的指针可以转换为“指向const对象”……等等。下面是可能发生于三层继承体系的一些转换
class Top { ... };
class Middle:public Top { ... };
class Bottom:public Middle { ... };
Top* pt1 = new Middle;
Top* pt2 = new Bottom;
const Top* pct2 = pt1; // 将Top*转换为const Top*
但如果用户想在自定的智能指针中模拟上述转换,会稍稍有点麻烦。我们希望以下代码通过编译:
template<typename T>
class SmartPtr
{
public:
explicit SmartPtr( T* realPtr );
...
};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);
SmartPtr<const Top> pct2 = pt1;
但是,同一个template的不同具现体之间并不存在与生俱来的固有关系(注:这里意指如果以带有base-derived关系的B,D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系),所以编译器视SmartPtr<Middle>和SmartPtr<Top>为完全不同的classes
2.基于1的需求,我们需要为SmartPtr写一个构造模板。这样的模板是所谓member function templates(常简称为member templates),其作用是为class生成函数:
template< typename T >
class SmartPtr
{
public:
template< typename U > // member template,
SmartPtr( const SmartPtr<U>& other ); // 为了生成copy构造函数
...
};
以上代码的意思是,对任何类型T和任何类型U,这里可以热气SmartPtr<U>生成一个SmartPtr<T>。这一类根据对象u创建对象t,而u和t的类型是同一个template的不同具现体,有时我们称之为泛化(generalized)copy构造函数。上面的泛化copy构造函数并未声明为explicit,是为了仿效原始指针类型之间的隐式转换,无需明白写出转型动作(cast)
3.要在“构造模板”实现代码中约束转换行为
template< typename T >
class SmartPtr
{
public:
template<typename U>
SmartPtr( const SmartPtr<U>& other ) // 以other的heldPtr
: heldPtr( other.get() ) { ... } // 初始化this的heldPtr
T* get() const { return heldPtr; }
...
private:
T* heldPtr; // 这个SmartPtr持有的内置(原始)指针
};
4.如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符
四十六、条款46-需要类型转换时请为模板定义非成员函数
1.当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”
四十七、条款47-请使用traits class表现类型信息
1.Traits classes使得“类型相关信息”在编译器可用。它们以templates和"templates特化"完成实现
2.整合重载技术(overloading)后,traits classes有可能在编译器对类型执行if...else测试
四十八、条款48-认识template元编程
1.Template metaprogramming(TMP 模板元编程)是编写template-based C++程序并执行于编译期的过程。一旦TMP程序结束执行,其输出,也就是从templates具现出来的若干C++源码,便会一如往常地被编译
2.TMP可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率
3.TMP可被用来生成“基于政策选择组合”(based on combinations of plicy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。
多线程环境下的内存管理,遭受单线程系统不曾有过的挑战。由于heap是一个可被改动的全局性次源,因此多线程系统充斥着发狂访问这一类资源的race conditions(竞速状态)出现机会。
四十九、条款49-了解new-handler的行为
1.当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,那是声明于<new>的一个标准程序库函数:
namespace std
{
typedef void (*new_handler)();
new_handler set_new_handler ( new_handler p ) throw();
}
2.设计良好的new-handler函数必须做以下事情:
[1]让更多内存可被使用
[2]安装另一个new-handler
[3]卸除new-handler
[4]抛出bad_alloc(或派生自bad_alloc)的异常
[5]不返回
3.Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常
五十、条款50-了解new和delete的合理替换时机
1.有许多理由需要写个自定的new和delete,包括改善效能、对heap运用错误进行调试、收集heap使用信息。
[1]为了检测运用错误
[2]为了收集动态分配内存之使用统计信息
[3]为了增加分配和归还的速度
[4]为了降低缺省内存管理器带来的空间额外开销
[5]为了弥补缺省分配器中的非最佳齐位(suboptimal alignment)
[6]为了将相关对象成簇集中
[7]为了获得非传统行为
五十一、条款51-编写new和delete时需固守常规
1.operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存要求,就该调用new-handler。它也应该有能力处理0 bytes申请。Class专属版本则还应该处理“比正确大小更大的(错误)申请”
2.operator delete应该在收到null指针时不做使用事。Class专属版本则还应该处理“比正确大小更大的(错误)申请”
五十二、条款52-写了placement new也要写placement delete
1.当你写了一个placement operator new,请确定也写出了对应的placement operator delete。如果没这么做,你的程序可能会发生隐微而时续时断的内存泄漏
2.当你声明placement new和placement delete,请确定不要无意识(非故意)地遮掩了它们的正常版本。
五十三、条款53-不要轻忽编译器的警告
1.严肃对待编译器发出的警告消息。努力在你的编译器的最高(最严苛)警告级别下争取“无任何警告”的荣誉
2.不要过度倚赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移值到另一个编译器上,你原本倚赖的警告信息有可能消失
五十四、条款54-让自己熟悉包括TR1在内的标准程序库
1.C++标准程序库主要机能由STL、iostreams、locales组成。并包含C99标准程序库
2.TR1添加了智能指针(例如tr1::shared_ptr)、一般化函数指针(tr1::function)、hash-based容器、正则表达式以及另外10个组件的支持
3.TR1自身只是一份规范。为获得TR1提供的好处,你需要一份实物。一个好的实物来源就是Boost
五十五、条款55-让自已熟悉Boost
1.Boost程序库对付的主题非常多,包括:
[1]字符串与文本处理
[2]容器
[3]函数对象和高级编程
[4]泛型编程
[5]模板元编程
[6]数学和数值
[7]正确性与测试
[8]数据结构
[9]语言间的支持
[10]内存
[11]其他包括CRC检验、日期和时间的处理、在文件系统上来回移动等
2.Boost是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的C++程序库开发。Boost在C++标准化过程中扮演深具影响力的角色
3.Boost提供许多TR1组件实现品,以及其他许多程序库。
发表评论
-
《C++必知必会》学习笔记
2012-11-24 23:40 2641《C++必知必会》学 ... -
《C++必知必会》学习笔记
2012-11-24 23:34 1《C++必知必会》学习笔 ... -
基本技术——贪心法、分治法、动态规划三大神兵
2012-11-03 19:30 0基本技术——贪心法、分治法、动态规划三大神兵 -
优先队列三大利器——二项堆、斐波那契堆、Pairing 堆
2012-11-03 13:12 35633优先队列三大利器——二项堆、斐波那契堆、Pairing ... -
优先队列三大利器——二项堆、斐波那契堆、Pairing 堆
2012-11-03 13:01 3优先队列三大利器——二项堆、斐波那契堆、Pairing 堆 ... -
排序算法群星豪华大汇演
2012-10-30 00:09 3142排序算法群星豪华大汇演 排序算法相对简单些,但是由于 ... -
分布排序(distribution sorts)算法大串讲
2012-10-29 15:33 4662分布排序(distribution sorts)算法大串讲 ... -
归并排序(merge sorts)算法大串讲
2012-10-29 10:04 8315归并排序(merge sorts)算法大串讲 ... -
交换排序(exchange sorts)算法大串讲
2012-10-29 00:22 4412交换排序(exchange sorts)算法大串讲 本 ... -
选择排序(selection sorts)算法大串讲
2012-10-28 12:55 3710选择排序(selection sorts)算法大串讲 本文内 ... -
插入排序(insertion sorts)算法大串讲
2012-10-28 11:30 2749插入排序(insertion sorts ... -
伸展树(Splay Tree)尽收眼底
2012-10-27 15:11 5520伸展树(Splay Tree)尽收眼底 本文内容 ... -
红黑树(Red-Black Tree)不在话下
2012-10-26 20:54 2234红黑树(Red-Black Tree) 红黑树定义 红黑树 ... -
平衡二叉树(AVL)原理透析和编码解密
2012-10-26 10:22 2999平衡二叉树(AVL)原理透析和编码解密 本文内容 ... -
Trie三兄弟——标准Trie、压缩Trie、后缀Trie
2012-10-26 01:45 10701Trie三兄弟——标准Trie ... -
Skip List(跳跃表)原理详解与实现
2012-10-25 17:49 54282Skip List(跳跃表)原理详解与实现 本文内容框 ... -
Bloom Filter一站式学习
2012-10-26 00:15 2971Bloom Filter一站式学习 ... -
大数据、海量数据处理之偷星摘月
2012-10-25 14:45 0第一部分、十道海量数据处理面试题 1、海量日志数据,提 ... -
最长公共子串、最长公共子序列、字符串编辑距离
2012-10-19 13:28 5175最长公共子串、最长公共子序列、字符串编辑距离 最长公共 ... -
连续子数组最大和和最长递增子序列
2012-10-19 11:35 8416本文内容框架: §1 连续子数组最 ...
相关推荐
《Effective C++》是一本由Scott Meyers所著的关于C++编程的书籍,该书广泛被认为是对C++程序员提高编程水平和解决实际问题提供了极好的指导。从给出的部分内容来看,读书笔记主要聚焦于以下几个知识点: 1. C++...
effective C++ 很好 很不错 最好的C++进阶资料
本文总结了Effective C++读书笔记,涵盖了C++的四个主要次语言:C、Object-Oriented C++、Template C++和STL。同时,文章还讨论了高效编程守则,包括使用const、enum和inline替换#define,使用const来告诉编译器和...
摘录了《Effective C++》 (Scott Meyers 著)中有参考价值的编写代码建议,方面阅读
《Effective Modern C++:改善C++11和C++14的42个具体做法(影印版)(英文版)》中包括以下主题:剖析花括号初始化、noexcept规范、完美转发、智能指针make函数的优缺点;讲解std∷move,std∷forward,rvalue引用和全局...
Effective C++是一本深入探讨C++编程实践的书籍,它提供了许多提高代码质量和效率的建议。以下是基于标题、描述和部分内容的关键知识点: 1. **虚函数的声明与使用**: - 在C++中,虚函数是实现多态性的关键。它们...
内容简介:有人说C++程序员可以分为两类,读过Effective C++的和没读过的。当您读过《Effective C++:改善程序与设计的55个具体做法(第3版)(中文版)(双色)》之后,就获得了迅速提升自己C++功力的一个契机。
自己整理的侯捷版本<<Effective C++ 改善程序与设计的55个具体做法>> 学习笔记.
Effective C++笔记1 本笔记总结了Effective C++的第1到第11条款,涵盖了C++语言的多个方面,包括构造函数、拷贝构造函数、拷贝赋值函数、const关键字、enum、inline函数、定义域、static变量、初始化、编译器生成的...
Roy T的读书笔记为我们提供了对这些核心概念的深入理解和应用实例。 1. **项1:考虑用智能指针替代原始指针** - 智能指针如`std::unique_ptr`和`std::shared_ptr`自动管理内存,避免了手动释放可能导致的悬挂指针...
Effective C++笔记1 本笔记概括了 Effective C++ 中的四十个规则的第一个部分,涵盖了 C++ 语言的基本概念、const 的使用、inline 函数的应用、对象的初始化等方面。 规则 1:将 C++ 视为语言联合体 -------------...
### Effective_C++_3rd...综上所述,Effective_C++_3rd笔记不仅是一系列编程技巧的集合,更是对C++语言特性和最佳实践的深入探索。通过遵循这些指导原则,开发者可以构建出更健壮、更高效、更易于维护的C++应用程序。
整体而言,Finix的精版《Effective STL》读书笔记覆盖了STL的关键知识点,包括各种容器的特性、迭代器的使用、以及如何高效地操作和管理容器。这对于任何想要深入了解并有效应用STL的C++程序员来说,都是宝贵的资源...
通过这些学习笔记,我们可以了解到《Effective C++》不仅为我们提供了C++编程中的一些基本和高级技巧,也教会我们如何遵循最佳实践,编写出更加高效、健壮的C++代码。学习这些知识点,对于提高C++编程能力和解决实际...
《More Effective C++》是C++编程领域的一本经典书籍,由Scott Meyers撰写,旨在帮助程序员提升C++编程的效率和质量。这本书是《Effective C++》的延续,提供了更多的编程技巧和最佳实践,涵盖了C++语言和标准库的深...
这份笔记基于Scott Meyers的经典著作《Effective C++》的第三版,书中包含了大量实用的编程实践和技巧,旨在帮助读者更好地理解和运用C++语言,避免常见陷阱,提升代码质量和效率。 1. **对象构造与析构**:笔记会...
整体来看,《Effective C++中文版》不仅是对C++编程语言特性的介绍,更是一本提供实用技巧和最佳实践的书籍。上述知识点的提炼,旨在帮助读者深入理解C++编程语言的高级特性,从而编写出更加高效、安全和可维护的...
通常C++要求你对所使用的任何东西提供一个定义式,但如果它是个class专属常量 又是static且为整数类型(integral type,例如int,char,bool),则可特殊处理。只要不取它们 的地址,你可以声明并使用它们而无需提供...