約 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
// 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
は (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)。これらは読みにくいためです。読みにくいものは、エラーが発生しやすいものでもあります。
次は?
ステートメントにはいくつかの規則が残っています。私の次の投稿はそれらから始まります。その後、算数のルールがよりスリリングになります。