内容简介:读了成员变量忘了初始化是一个相当经典的错误,甚至《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);
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Introduction to Computation and Programming Using Python
John V. Guttag / The MIT Press / 2013-7 / USD 25.00
This book introduces students with little or no prior programming experience to the art of computational problem solving using Python and various Python libraries, including PyLab. It provides student......一起来看看 《Introduction to Computation and Programming Using Python》 这本书的介绍吧!