内容简介:读了成员变量忘了初始化是一个相当经典的错误,甚至《Effective C++》中还专门列了一条来讲这个事情。在工作中,我也看到过这种错误,同时对一个新增的功能加上了开关的控制逻辑,但是忘了对这个开关的标识进行初始化,导致了。而且因为成员变量不初始化,那它的初始值是随机的,所以导致线上的表现是时灵时不灵,增加了 debug 的难度。当然增加 Coverity 扫描提早发现这个问题,当时项目上线比较急就直接跳过了这一步。从 C++11 开始支持在声明成员变量的时候直接初始化,有了这个特性之后,我已经养成了所有成
读了 《C++ 的门门道道 | 技术头条》 这篇文章之后有很多同感,可以说是近期看过的最好的技术小 tips 文章了。按照这篇文章里面讲到的几点,我也来说一下我的感受。
成员变量初始化
成员变量忘了初始化是一个相当经典的错误,甚至《Effective C++》中还专门列了一条来讲这个事情。在工作中,我也看到过这种错误,同时对一个新增的功能加上了开关的控制逻辑,但是忘了对这个开关的标识进行初始化,导致了。而且因为成员变量不初始化,那它的初始值是随机的,所以导致线上的表现是时灵时不灵,增加了 debug 的难度。当然增加 Coverity 扫描提早发现这个问题,当时项目上线比较急就直接跳过了这一步。
从 C++11 开始支持在声明成员变量的时候直接初始化,有了这个特性之后,我已经养成了所有成员变量都直接在声明的时候初始化。
class Ad { private: unsinged int lifetime = 10000; };
sort() 里的坑
这个还真出过特别经典的坑,有一次线上事故就是一个稳定跑了很久的逻辑,突然出现了 core,而且是持续地 core 在 sort 上。花了很长时间排查,最后才意识到实现 sort 比较函数的时候没有保证 严格弱序(strict weak order) ,比较两个对象的属性时用了 <=
。这里就涉及到 C++ 中 sort 的实现。细节之后会写一篇文章来讲,简单说来就是 STL sort 核心 排序 算法是快排,在依据 pivot 调整元素位置时采用的实现方式如下:
while (true) { while (__comp(*__first, __pivot)) ++__first; --__last; while (__comp(__pivot, *__last)) --__last; if (!(__first < __last)) return __first; std::iter_swap(__first, __last); ++__first; }
重点就在于 while (__comp(*__first, __pivot)) ++__first;
,当整个容器里的元素都相等时,就会导致 __first 这个迭代器越界,程序就 core 了。
操作符短路
对我更常用的场景: if (!stack.empty() && stack.top() == 0)
,这恰恰是利用短路来合并判断。
别让循环停不下来
这个有个经典场景,一个乱序的 vector 里面,我要找到第一个递增序列的最后一个元素,很容易写成这样的代码:
while (i < ve.size() - 1 && ve[i] <= ve[i + 1]) i++;
这里如何传入的 ve 是个空 vector,那么就会成为超大循环,因为 vector::size() 返回的是 unsigned int,根据数值类型传递,ve.size() - 1 的类型也是 unsigned int,那么就会返回一个很大的数,导致 while 陷入超大循环。
理解 vector 的实现
vector 可以说是在日常开发中使用频率最高的容器了,支持下标访问,动态扩容,二分查找的效率,C++11 之后支持移动构造,这些优点都让它非常好用。vector 的坑都集中在它的动态扩容上,理解它动态扩容的机制可以在开发中避开这些坑。
vector 动态扩容的两个特点:
-
vector 扩容是按照 2 的指数倍往上翻的,也就是 2, 4, 8, 64, 128, ……。
-
动态扩容时是会全量复制一遍现有的所有元素到新分配的内存中。
根据这两个特点,结合 vector 的其他特性得到的 tips:
-
尽量预先分配好 vector 的空间,使用 reserve() 预分配空间,避免多次扩容。
-
不要存在大对象,扩容的时候会全量复制,额外的性能开销很大。
-
不要保存指向 vector 内部对象的指针,扩容时对象地址会发生变化。
-
reserve() 是提前分配空间,此时不能直接用下标索引访问(如果用基本类型倒是能访问,但是这种行为仍然是未定义的)。
有时候真的不必用 std::unorder_map
组里有一个项目升级到 C++11 之后,一窝蜂地使用 unordered_map,但是其实对于小数据量,比如本次请求命中的一些配置,其实数据量基本都在 10 项以内,那其实用 map 就完全够用了,unorder_map 查找的效率当然是高的,但是也要认识到它维护一个红黑树额外付出的性能代价。
慎用用short,char
有些人写代码的时候有一种倾向,就是能省则省,能用 int 的绝不用 long,能用 short 的绝不用 int。但是其实有些情况下 short 并不能节省空间(字节对齐),还导致过度「优化」,导致之后要重写,或者实际的取值不符合设计导致溢出。
避免箭头型代码
什么是「箭头型代码」?见下图:
这种代码其实在业务复杂的场景下并不少见,酷壳上有一篇文件专门讲过如何重构这种代码: 《如何重构“箭头型”代码》 。
我在实际项目中应用比较多是利用 while (0) 来规避这种代码。
在项目经常遇到的场景是对一连串条件进行判断,不符合条件的分支需要打印日志,示例代码如下:
if (conditionA()) { if (conditionB()) { if (conditionC()) { if (conditionD()) { // do something } else { // log } } else { // log } } else { // log } } else { // log }
这种情况下用 do-while(0)
可以进行非常好的重构,重构之后的代码如下:
do { if (!conditionA()) { // log break; } if (!conditionB()) { // log break; } if (!conditionC()) { // log break; } if (!conditionD()) { // log break; } } while (0);
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
模式识别
(希)Sergios Theodoridis、(希)Konstantinos Koutroumbas / 电子工业出版社 / 2010-2 / 75.00元
本书全面阐述了模式识别的基础理论、最新方法以及各种应用。模式识别是信息科学和人工智能的重要组成部分,主要应用领域有图像分析、光学字符识别、信道均衡、语言识别和音频分类等。本书在完美地结合当前的理论与实践的基础上,讨论了贝叶斯分类、贝叶斯网络、线性和非线性分类器设计、上下文相关分类、特征生成、特征选取技术、学习理论的基本概念以及聚类概念与算法。与前一版相比,增加了大数据集和高维数据相关的最新算法,这......一起来看看 《模式识别》 这本书的介绍吧!