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);

參考文章:

相關文章
相關標籤/搜索