C++ Tips

读了《C++ 的门门道道 | 技术头条》这篇文章以后有不少共鸣,能够说是近期看过的最好的技术 tips 文章了。按照这篇文章里面讲到的几点,结合工做上实际遇到的问题,我也来讲一下个人感觉。html

<!-- more -->c++

成员变量初始化

成员变量忘了初始化是一个至关经典的错误,甚至《Effective C++》中还专门列了一条来说这个事情。在工做中,我就看到过这种错误,同事对一个新增的功能加上了开关控制的逻辑,可是忘了对这个开关的标识进行初始化,致使了一条分支逻辑失效。并且由于 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 调整元素位置时采用的实现方式以下:shell

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 陷入超大循环。spa

理解 vector 的实现

vector 能够说是在平常开发中使用频率最高的容器了,支持下标访问,动态扩容,二分查找的效率,C++11 以后支持移动构造,这些优势都让它很是好用。vector 的坑都集中在它的动态扩容上,理解它动态扩容的机制能够在开发中避开这些坑。debug

vector 动态扩容的两个特色:

  1. vector 扩容是按照 2 的指数倍往上翻的,也就是 2, 4, 8, 64, 128, ……。
  2. 动态扩容时是会全量复制一遍现有的全部元素到新分配的内存中。

根据这两个特色,结合 vector 的其余特性获得的:

  1. 尽可能预先分配好 vector 的空间,使用 reserve() 预分配空间,避免屡次扩容。
  2. 不要在 vector 里存储大对象,扩容的时候会全量复制,额外的性能开销很大。
  3. 不要保存指向 vector 内部对象的指针,扩容时对象地址会发生变化。
  4. reserve() 是提早分配空间,此时不能直接用下标索引访问(若是用基本类型却是能访问,可是这种行为仍然是未定义的)。

有时候真的没必要用 std::unorder_map

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

慎用short,char

由于一开始设计的数值类型过于严格而致使的重写,我遇到过不止一次了。

有些人写代码的时候有一种倾向,就是能省则省,能用 int 的毫不用 long,能用 short 的毫不用 int。可是其实有些状况下 short 并不能节省空间(字节对齐),还致使过分「优化」,致使逻辑变化以后要重写,或者实际的取值不符合设计致使溢出。

避免箭头型代码

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

箭头型代码,图片来源:CoolShell

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

我在实际项目中应用比较可能是利用 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);

参考文章:

相关文章
相关标签/搜索