C++ Core Guideline 筆記02

I.1 Make interface explicit

不好的示例->

int round(double d)
{
    return (round_up) ? ceil(d) : d;    // don't: "invisible" dependency
}

這個例子我主要理解是這樣的,函數名是round但是控制邏輯中有在調用時非常可能被忽略的全局變量round_up。我理解round_up應該出現在參數列表裏。但是這種設計非常有可能出現在類的成員函數裏,設想如下代碼片段(非原文)

class A{
int round(double d) {
	return (round_up) ? ceil(d) : d;
}

round_up = true;
};

round()的調用點,我們還是要檢查成員變量round_up的值才能期待round()給出正確的行爲。原文最後有一個看似這種的guideline,就是如果函數比較簡單,那麼它不應該執行基於全局變量(namespace級別的變量)的邏輯。

I.4 Make interfaces precisely and strongly typed

作者強調了函數參數最好要typed,

void draw_rectangle(Point top_left, Point bottom_right);
void draw_rectangle(Point top_left, Size height_width);

draw_rectangle(p, Point{10, 20});  // two corners
draw_rectangle(p, Size{10, 20});   // one corner and a (height, width) pair

所有上述例子中,當參數直接使用數字時,很難推斷出參數的含義。作者推薦使用enum作爲boolean flag使用。

enable_lamp_options(lamp_option::on | lamp_option::animate_state_transitions);

所以,當一個函數接受超過兩個bool變量作爲參數時,這個函數的interface可能需要重新設計。作者再次強調參數的數值單位的重要性。在CppCon2017 Bjarne Stroustrup上做的報告有特殊講到數值單位引起的bug導致一個NASA的火星衛星沒有進入預定軌道,科研人員15年的研究付之東流。

I.5 State preconditions

推薦了GSLExpects()函數

I.6 Prefer Expects() for expressing preconditions

再次推薦使用Expects(),但是C++20估計都沒有標準化。

I.7 State postconditions

下面代碼爲了確保res作爲整形不會溢出。這種情況在進行整數運算時可能會出現,例如對RGB圖像進行處理時,大部分時間數據對象可能是uint8_t類型,數值在0-255之間。

int area(int height, int width)
{
    auto res = height * width;
    Ensures(res > 0);
    return res;
}

另一個例子

void f()    // better
{
    char buffer[MAX];
    // ...
    memset(buffer, 0, sizeof(buffer));
    Ensures(buffer[0] == 0);
}

此實例中,不加入Ensures()時,編譯器優化階段可能會remove掉memset()
此外原文列出了一個多線程mutex鎖的示例

void manipulate(Record& r)    // best
{
    lock_guard<mutex> _ {m};
    // ...
}

改示例確保m將在函數結束時釋放,無論是否發生Exception。

I.9 If an interfaceis a template, document its parameters using concepts.

使用concept在編譯期間對模板參數進行檢查。特指出GCC6.1之後都對concept進行了支持。

I.10 Use exceptions to signal a failure to perfomr a required task

作者認爲對於定義好的error,應該拋出異常而不是利用返回值表示狀態。特別指出performance 並不是拒絕使用exception的好理由。若可以今早檢查異常,可以提升critical code的performance。

I.11 Never transfer ownership by a raw pointer or reference

這裏強調了rvalue 和move semantics。

I.12 Declare a pointer that must not be null as not_null

Wow, that’s nice.

int length(const char* p);            // it is not clear whether length(nullptr) is valid

length(nullptr);                      // OK?

int length(not_null<const char*> p);  // better: we can assume that p cannot be nullptr

int length(const char* p);            // we must assume that p can be nullptr

not_null定義在GSL中。

I.13 Do not pass an array as a single pointer

如下的代碼可能產生多種錯誤

void copy_n(const T* p, T* q, int n); // copy from [p:p+n) to [q:q+n)

包括q的大小不足n,p的內容少於n。使用下述代碼會更好

void copy(span<const T> r, span<T> r2); // copy r to r2

I.22 Avoid complex initialization of global objects

原文這裏編號一下跳到了I.22。原文指出全局變量的初始化次序是不確定的,所以儘量不要使用。全局變量初始化依賴函數應該是constexpr

I.23 Keep the number of function arguments low

當函數的參數過多時,非常肯恩意味着“one function, one responsibility”的規則被打破。同時,過多的參數說明這些參數應當被抽象成type。
作者認爲,4個參數以上的,都算是參數數量太多。

I.24 Avoid adjacent unrelated parameters of the same type

意思很簡單,參數表裏,如果兩個參數的類型完全一致,但是參數次序不能調換,那麼這種參數表容易被用錯。
考慮將參數表定義成一個struct,這樣在調用函數時需要用變量名指定函數的每一個參數,降低了發生錯誤的風險。但是這樣做代碼邊長了。有點像python的keyword argument。

I.25 Prefer abstract classes as interfaces to class hierarchies

意思也比較簡單,不定義base class,而只用abstract class。在abstract class中,沒有成員變量,所有藉口都是=0的虛函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章