⚛️

C++ Core Guidlines を自分の言葉で学ぶ -Philosophy:哲学 -

2023/04/23に公開

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#p-philosophy

P.1 コードでアイデアを表現する

コンパイラはコメントを読まないし、プログラマもコメントを読むとは限らない。

class Date{
public:
  Month month() const; //OK:月を返すこと、状態を変更しないことが明確
  int month();         //NG:整数が表すのは何月か、0や-1が使われるのか...推測になる。状態を変更する可能性が捨てられない。
};
void change_speed(Speed s);  //OK
void change_speed(double s); //NG:sの単位が不明瞭
change_speed(2.3_ms); //OK:単位が(m/s)だとわかる
change_speed(2.3);   //NG:sの単位が不明瞭

※_msはユーザ定義リテラル
cppreference → https://cpprefjp.github.io/lang/cpp11/user_defined_literals.html
確認 →
https://wandbox.org/permlink/QZX4rsPHjJwyZ629

P.2 ISO Standard C++で書く

環境依存の拡張機能や、環境依存の未定義動作は使わないようにする

P.3 Express Intent (意図を示す)

型や言語機能を使い、誤読の可能性を減らす

//NG: indexがループ処理以降も生き残り、何かに使われる可能性がある(実際はともかく)
int index = 0;
while(index < v.size()){
   //...
}

//OK: ループ処理であることが明確
for(const auto& x: v){ ... }
draw_line(int,int,int,int); //NG:(x1,y1,x2,y2)か(x,y,w,h)か不明瞭
draw_line(Point, Point);    //OK

P.4 静的な型安全が望ましい

理想的には完全に静的にでありたいが、実際にはいくつかの領域で不可能である。いくらかは代替案によって、問題を軽減することができる。
||代替案|
|共用体|std::variant|
|cast|テンプレート(※1)|
|配列のdecay|gsl::spanを使う(※2)|
|範囲エラー|gsl::spanを使う(※2)|
|nallow cast|gsl::narrow_castを使う(※3)|
※1 値をcastして処理するのではなく、テンプレートによって型をそのまま使って処理できる設計を使おう...という意味だと思う
※2 gslはCppCoreGuidelinesが提供するライブラリ。gsl::spanは配列やvectorなどの動的/静的配列の「区間」を安全に使うための型
※3 情報の損失を伴う数値のcast。gsl::narrow/gsl::narrow_castはデータの損失を厳密に扱うcast。narrowは損失がある場合にexceptionを投げるcast。narrow_castはデータの損失があってもそれを許容することを明示するcast。

P.5 実行時よりコンパイル時にエラーチェックしたい

//NG: 実行時にビット長を検査している
int bitCount = 0;
for(Int i=1; i!=0; i<<=1){ bitCount++;}
if(bitCount < 32){cerr << "too small";}
//OK: コンパイル時にビット長を検査している
static_assert(sizeof(Int) >= 4);

P.6 コンパイル時にエラーチェックできないなら実行時にエラーチェックする

特筆なし

P.7 実行時エラーチェックは、早期のキャッチを目指す

nullptrアクセスや不正な参照エラーになるよりも早く検出できるなら、そのほうが良い。謎のクラッシュを回避しよう。

//NG: pの範囲外にアクセスしてエラーになる。(または運良く/運悪く、エラーにならない)
void increment(int* p, int n){
  for(int i=0;i<n;i++) ++p[i];
}

//OK: gsl::spanの生成時点で不正な範囲をさせないようになっている
void increment(span<int> p){
  for(int& x: p) ++x;
}

ただし、過剰なエラーチェックは避けたい。
同じチェックが複数回行われるフローや、計算量が本来の処理を超えるようなチェックを避けたい。

P.8 リソースをリークさせない

  • fopenで開いたファイルをfcloseせずに終わるとそれはリソースリーク。
  • newしたオブジェクトをそのまま提供しない、
    RAIIの考え方を徹底する

P.9 時間、メモリを無駄にしない

理由のない、ただの無駄は排除すべし。

//NG
void lower(char* str){
   for(int i=0;i<strlen(str);i+){s[i] = tolower(s[i]0);}
}

P10. 可変より不変。

変数よりも定数のほうが推論が容易。
(定数と不変性の項目を参照のこと。)

P11. 乱雑な構築処理を書かない。カプセル化する。

オブジェクトの生成において、今やmallocなどの低レベルなAPIを使う必要はない。
標準ライブラリやgslなど、低レベルなAPIを隠蔽して使用できる道具がが多数用意されている。それらはユーザよりも専門知識のある人々によって設計・実装されている。

P12. 支援ツールを適切に使用する

  • 静的解析ツール
  • Concurrency Tool (?)
  • テスト支援ツール
    使おう

P13. ライブラリを適宜使用する

  • 標準ライブラリ
  • gslライブラリ

P:哲学は、以上

Discussion