C++ Tips

栏目: C++ · 发布时间: 5年前

内容简介:读了成员变量忘了初始化是一个相当经典的错误,甚至《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 动态扩容的两个特点:

  1. vector 扩容是按照 2 的指数倍往上翻的,也就是 2, 4, 8, 64, 128, ……。

  2. 动态扩容时是会全量复制一遍现有的所有元素到新分配的内存中。

根据这两个特点,结合 vector 的其他特性得到的 tips:

  1. 尽量预先分配好 vector 的空间,使用 reserve() 预分配空间,避免多次扩容。

  2. 不要存在大对象,扩容的时候会全量复制,额外的性能开销很大。

  3. 不要保存指向 vector 内部对象的指针,扩容时对象地址会发生变化。

  4. reserve() 是提前分配空间,此时不能直接用下标索引访问(如果用基本类型倒是能访问,但是这种行为仍然是未定义的)。

有时候真的不必用 std::unorder_map

组里有一个项目升级到 C++11 之后,一窝蜂地使用 unordered_map,但是其实对于小数据量,比如本次请求命中的一些配置,其实数据量基本都在 10 项以内,那其实用 map 就完全够用了,unorder_map 查找的效率当然是高的,但是也要认识到它维护一个红黑树额外付出的性能代价。

慎用用short,char

有些人写代码的时候有一种倾向,就是能省则省,能用 int 的绝不用 long,能用 short 的绝不用 int。但是其实有些情况下 short 并不能节省空间(字节对齐),还导致过度「优化」,导致之后要重写,或者实际的取值不符合设计导致溢出。

避免箭头型代码

什么是「箭头型代码」?见下图:

C++ Tips

这种代码其实在业务复杂的场景下并不少见,酷壳上有一篇文件专门讲过如何重构这种代码: 《如何重构“箭头型”代码》

我在实际项目中应用比较多是利用 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元

本书全面阐述了模式识别的基础理论、最新方法以及各种应用。模式识别是信息科学和人工智能的重要组成部分,主要应用领域有图像分析、光学字符识别、信道均衡、语言识别和音频分类等。本书在完美地结合当前的理论与实践的基础上,讨论了贝叶斯分类、贝叶斯网络、线性和非线性分类器设计、上下文相关分类、特征生成、特征选取技术、学习理论的基本概念以及聚类概念与算法。与前一版相比,增加了大数据集和高维数据相关的最新算法,这......一起来看看 《模式识别》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器