C++ コア ガイドライン:ステートメントのルール

約 15 のステートメントのルールに進む前に、式の 2 つのルールを終了させて​​ください。どちらのルールも、未定義の動作からプログラムを保護するのに役立ちます。

式に関する残りの 2 つのルールは次のとおりです。

ES.64:T{e} を使用 構造表記

値の構築に T{e} を使用する理由は明らかです。 T(e) または (T)e とは対照的に、T{e} は縮小変換を許可しません。絞り込み変換とは、データの精度が損なわれる変換です。これはほとんどの場合であり、あなたの意図ではないと思います。ガイドラインの例を見てください。

void use(char ch, double d, char* p, long long lng){
 int x1 = int{ch}; // OK, but redundant
 int x2 = int{d}; // error: double->int narrowing; use a cast if you need to
 int x3 = int{p}; // error: pointer to->int; use a reinterpret_cast if you really need to
 int x4 = int{lng}; // error: long long->int narrowing; use a cast if you need to (1)

 int y1 = int(ch); // OK, but redundant
 int y2 = int(d); // bad: double->int narrowing; use a cast if you need to
 int y3 = int(p); // bad: pointer to->int; use a reinterpret_cast if you really need to (2)
 int y4 = int(lng); // bad: long->int narrowing; use a cast if you need to

 int z1 = (int)ch; // OK, but redundant
 int z2 = (int)d; // bad: double->int narrowing; use a cast if you need to
 int z3 = (int)p; // bad: pointer to->int; use a reinterpret_cast if you really need to (3)
 int z4 = (int)lng; // bad: long long->int narrowing; use a cast if you need to 
}

以下は、gcc が特別なフラグなしで提供するものです。

コンパイラの実行結果を注意深く読むと、いくつかの興味深い事実に気付くでしょう。

  • 式 (1) は、最初のコード ブロックでのみ警告を出します。前の 2 つの式はエラーになります。
  • 式 (2) と (3) のみがエラーになります。 2 番目と 3 番目のコード ブロックの他の変換では、警告さえ出ません。

T(e1, e2) または T{e1, e2} で値を構成する場合、覚えておく必要がある特別なルールがあります。競合する 2 つのコンストラクターを持つクラスがある場合はどうなりますか? 1 つのコンストラクターは 2 つの int (MyVector(int, int)) を受け入れ、もう 1 つのコンストラクターは std::initializer_list (MyVector(std::initializer_list)) を受け入れますか?興味深い質問は次のとおりです:MyVector(1, 2) を呼び出すか、MyVector{int, int} を 2 つの int のコンストラクターまたは std::initalizer_list を持つコンストラクターに呼び出しますか?

// constructionWithBraces.cpp

#include <iostream>

class MyVector{
public:
 MyVector(int, int){
 std::cout << "MyVector(int, int)" << std::endl;
 }
 MyVector(std::initializer_list<int>){
 std::cout << "MyVector(std::initalizer_list<int>)" << std::endl;
 }
};

class MyVector1{
public:
 MyVector1(int, int){
 std::cout << "MyVector1(int, int)" << std::endl;
 }
};

class MyVector2{
public:
 MyVector2(int, int){
 std::cout << "MyVector2(int, int)" << std::endl;
 }
};

int main(){
 
 std::cout << std::endl;
 
 MyVector(1, 2); // (1)
 MyVector{1, 2}; // (2) 
 
 std::cout << std::endl;
 
 MyVector1{1, 2}; // (3)
 
 std::cout << std::endl;
 
 MyVector2(1, 2); // (4)
 
 std::cout << std::endl;
 
}

これがプログラムの出力です。呼び出し (1) は、2 つの int を使用してコンストラクターを呼び出します。呼び出し (2) std::initializer_list を持つコンストラクター。 MyVector1{1, 2} (3) を呼び出す場合、コンストラクタ MyVector1(1, 2) は一種のフォールバックです。

は (4) には当てはまりません。この場合、 std::initializer_list を持つコンストラクターはフォールバックではなく、です。

std::initializer_list を引数として取るコンストラクターは、多くの場合、シーケンス コンストラクターと呼ばれます。

例の MyVector でクラスを呼び出した理由を知っていますか?その理由は、次の 2 つの式の動作が異なるためです。

std::vector<int> vec(10, 1); // ten elements with 1
std::vector<int> vec2{10, 1}; // two elements 10 and 1

最初の行は、値 1 を持つ 10 要素のベクトルを作成します。 2 行目は、値 10 と 1 を持つベクトルを作成します。

ES.65:無効なポインターを逆参照しない

このように言いましょう。 nullptr などの無効なポインターを逆参照すると、プログラムの動作が未定義になります。これは厄介です。これを回避する唯一の方法は、ポインターを使用する前に確認することです。

void func(int* p) {
 if (p == nullptr) { // do something special
 }
 int x = *p;
 ...
}

どうすればこの問題を克服できますか?ネイキッド ポインターは使用しないでください。 std::unique_ptr や std::shared_ptr などのスマート ポインター、または参照を使用します。最新の C++ におけるさまざまな種類の所有権セマンティックについては、既に記事を書いています。詳細については、C++ コア ガイドライン:リソース管理のルールをご覧ください。

ギアを切り替えましょう。

ステートメントのルール

ステートメントのルールは非常に明白です。したがって、私はそれを短くすることができます.

  • 選択肢がある場合は、if ステートメントよりも switch ステートメントを優先する必要があります (ES.70)。これは、switch ステートメントの方が読みやすく、より最適化できる可能性があるためです。
  • for ループとは対照的に、範囲ベースの for ループ (ES.71) にも同じことが当てはまります。 1 つ目は、範囲ベースの for ループの方が読みやすいことです。2 つ目は、ループ中にインデックス エラーを起こしたり、インデックスを変更したりできないことです。
  • 明らかなループ変数がある場合は、while ステートメントの代わりに for ループを使用する必要があります (ES.72)。そうでない場合は、while ステートメントを使用する必要があります (ES.73)。

(1) for ループを好む場合と (2) while ステートメントを好む場合の例を示します。

for (gsl::index i = 0; i < vec.size(); i++) { // (1)
 // do work
}

int events = 0; // (2)
while (wait_for_event()) { 
 ++events;
 // ...
}

  • ループ変数は for ループで宣言する必要があります (ES.74)。これは、for ループだけでなく、C++17 以降の if ステートメントまたは switch ステートメントにも当てはまります。詳細はこちら:C++17 - コア言語の新機能
  • do ステートメント (ES.75)、goto ステートメント (ES.76) を避け、ループ内での break と continue の使用を最小限に抑えます (ES.77)。これらは読みにくいためです。読みにくいものは、エラーが発生しやすいものでもあります。

次は?

ステートメントにはいくつかの規則が残っています。私の次の投稿はそれらから始まります。その後、算数のルールがよりスリリングになります。