More Effective C++ 35 条建议

条款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>

条款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的包容器;算法是对包容器进行处理的函数,并使用选择子来实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值