more effictive C++

条款1 仔细区别指针和引用

引用和指针的不同点

  1. 引用在定义时必须初始化,指针没有要求
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  3. 没有NULL引用,但是有NULL指针,也正因为如此,引用可能效率更高些
  4. sizeof的含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  6. 有多级指针,但没有多级引用
  7. 访问实体的方式不同,指针需要显式解引用,引用由编译器自己处理
  8. 引用比指针使用起来相对更安全
  9. 可以有const指针,但是没有const引用
  10. 由于没有空引用,所以引用在使用起来可能效率较高(不需要对其有效性进行测试)

相同点

  1. 他们都需要间接引用其他对象

结论
当你知道你需要指向某个东西,而且绝不会改变指向其他东西,或者当你实现一个操作而其语法需求无法通过指针完成时(operator[])你应该选择引用,任何其他时候都应该选择指针


条款2 最好使用C++转型操作符

旧式转型的缺点

  1. 因为它几乎能够完成任何类型转换,所以不能精确的指明意图
  2. 辨识度低

新式转换的优点

  1. 严谨的意义和易辨识度
  2. 编译器可能得到诊断转型错误

新式类型转换


条款3 绝对不要以多态方法处理数组

C++规范中说,通过基类指针删除一个由派生类组成的数组其结果是未定义的。
多态和指针不能混用,数组对象几乎总是会涉及指针的算术运算,所以数组和多态不要混用


条款4 非必要不提供默认构造函数

如果一个类没有默认构造,那么使用上有哪些限制?

  1. 创建数组的时候,一般没有办法可以为数组中的对象的成员变量初始化
  2. 在模板类的使用过程中会遇到问题,因为模板类被实例化的时候要求目标类型必须有一个默认构造,因为在那些模板内几乎总是会产生一个模板类型的数组(该问题可以通过技术的提高来消除)
  3. 虚基类如果缺少了默认构造,那么与这种类合作十分心累,因为虚基类构造函数的参数必须由产生该对象的派生层次最深的类提供。

结论
如果类的构造函数可以保证对象所有字段都被正确初始化,上述所有成本便都可以免除,如果默认构造函数无法提供这种保证,那么最好避免让默认构造函数出现,虽然这可能对类的使用方式带来某种限制,但是同时带来了一种保证,即当你真的使用了这样的类,你可以预期他们所产生的对象会被完全地初始化,实现上亦富有效率。


条款5 对定制的类型转换函数保持警觉

对于内置类型的转换我们无能为力,因为他们是语言提供的,但是我们对于自己定义的类型就可以选择是否提供某些函数供编译器拿来做隐式类型转换之用
两种函数允许编译器执行类型转换:单参数的构造函数和隐式类型转换操作符。
单参数的构造函数是指能够以单一自变量调用成功的构造函数,如此的构造函数可能拥有单一参数,也可能拥有多个参数,并且除了第一个参数之外都有默认值。
隐式类型转换操作符是一个成员函数,函数名为operator 类型名,你不能为此函数指定返回值类型,因为其返回值类型已经在函数名上反映出来了
为什么最好不要提供任何类型转换函数?
会导致从未打算也未预期的情况下,此类函数可能会被调用, 而其结果可能是不正确、不直观的程序行为,很难调试。
一般来说越有经验的C++程序员越可能避免使用类型转换操作符。

通过单参数构造函数完成的隐式转换较难消除,此外这些函数造成的问题在许多方面比隐式类型转换操作符的情况更不好对付。只要不声明隐式类型转换操作符,便可以将它所带来的害处避免,但是单参数构造函数却不那么容易去除,因为有时候你可能真的需要一个单参数的构造函数提供给客户,与此同时,你可能希望阻止编译器部分青红皂白的去调用该构造函数,解决方案如下:

  1. 在函数声明为explicit,编译器便不能因隐式类型转换而调用他们,不过显示类型转换是允许的
  2. 因为没有任何一个转换程序可以内涵一个以上的用户定制转换行为

允许编译器执行隐式类型转换,害处多过好处,所以不要提供转换函数,除非你确定需要他们,对于单参数构造函数我们先给它加上explicit,如果后面我们确定需要使用其隐式类型转换特性,再去掉该关键字


条款6 区别递增递减操作符的前置和后置形式

操作符的前置和后置式返回不同的类型,前置式返回一个引用,后置式返回一个const对象
令函数返回一个const对象是否合理?后置式的递增和递减操作符就是一个例子,否则obj++++就是合法的,首先其与内置类型不一致,其次并不能达到加两次的效果(违反直觉,引起混淆),所以返回const对象。
设计类的一个法则是,一旦有疑虑,尝试看int的行为,并按照该行为去设计我们的类
对于我们自定义类型,我们应该优先使用前置的递增和递减操作符,因为相较于后置的递增和递减操作符而言,他们有更高的效率
有个原则:后置式的递增或者递减操作符的实现应该以其前置式兄弟为基础,如此一来只需要维护前置式版本,因为后置式版本会自动调整为一致的行为。

条款7 千万不要重载&&,||和,操作符

当我们重载前两个的时候,函数调用语义会替代掉所谓的骤死式语义,第一,当函数调用动作被执行,所有参数值都必须评估完成,所以当我们调用operator&&和operator||时,两个参数都已评估完成,换句话说没有什么骤死式语义,第二,C++规范中并未明确定义函数调用动作中各参数的评估顺序,而骤死式语义中总是由左向右评估其自变量。
如果重载&&和||,就没有办法提供程序员预期的某种行为模式,所以不要重载&&和||
对于逗号表达式来说,无法保证左侧表达式一定比右侧表达式更早被评估,因为两个表达式都被当做函数调用时的参数传递给该函数 ,而你无法控制一个函数的自变量的评估顺序。
无法被重载的操作符
. .* :: ?: new delete sizeof typeid static_cast dynamic_cast const_cast reinterpret_cast

条款8 了解各种不同意义的new和delete

如果你希望将对象产生于堆上,请使用new operator,因为它不但分配内存而且为该对象调用一个构造函数,如果你只打算分配内存,请调用operator new,这样的话就没有任何构造函数被调用,如果你打算在堆对象产生时自己决定内存分配方式,那么就自己写一个operator new,并使用new operator,它将会自动调用你所写的operator new。如果打算在已分配的内存中构造对象,请使用placement new。
如果你打算处理原始的、为设置初值的内存,应该完全回避new operator和delete operator,改调用operator new取得内存并以operator delete归还内存。
如果你使用placement new,在某块内存中产生对象,你应该避免 对那块内存使用delete operator,因为delete operator会调用operator delete来释放内存,但是该内存内含的对象最初并非是由operator new分配得来的。毕竟placement new只是返回它所接收的指针而已,所以为了抵消该对象构造函数的影响,你应该先调用该对象的析构函数。

发布了106 篇原创文章 · 获赞 3 · 访问量 2590

猜你喜欢

转载自blog.csdn.net/weixin_43718250/article/details/103764499