C++ コア ガイドライン:宣言と初期化

C++ コア ガイドラインの式とステートメントの規則について、ツアーを続けましょう。この投稿は、宣言と初期化に関するものです。

正直なところ、この投稿のルールのほとんどは非常に明白ですが、多くの場合、非常に興味深い洞察が得られます。したがって、この投稿では主にこれらの特別な側面について書きます。今日のルールは次のとおりです。

  • ES.11:117 を使用 型名の冗長な繰り返しを避けるため
  • ES.12:ネストされたスコープで名前を再利用しない
  • ES.20:常にオブジェクトを初期化する
  • ES.21:必要になる前に変数 (または定数) を導入しない
  • ES.22:初期化する値が得られるまで変数を宣言しないでください
  • ES.23:128 を優先 -初期化構文
  • ES.24:137 を使用する ポインターを保持する

詳細はこちら。

ES.11:149 を使用 タイプ名の冗長な繰り返しを避けるため

ガイドラインの例は、私には有望ではありません。じゃあ、もう一つあげますね。 auto を使用すると、コードを変更するのが簡単になる場合があります。

次の例は、完全に auto に基づいています。タイプについて考える必要がないため、エラーを起こすことはできません。これは、最後に res の型が int になることを意味します。

auto a = 5;
auto b = 10;
auto sum = a * b * 3;
auto res = sum + 10; 
std::cout << typeid(res).name(); // i

リテラル b を int から double に変更する場合 (2)、または (3) int リテラルの代わりに float リテラルを使用する場合。問題ない。自動的に処理されます。

auto a = 5;
auto b = 10.5; // (1)
auto sum = a * b * 3;
auto res = sum * 10; 
std::cout << typeid(res).name(); // d
 
auto a = 5;
auto b = 10;
auto sum = a * b * 3.1f; // (2)
auto res = sum * 10; 
std::cout << typeid(res).name(); // f

ES.12:ネストされたスコープで名前を再利用しない

これは非常に明白なルールの 1 つです。読みやすさと保守の理由から、ネストされたスコープで名前を再利用しないでください。

// shadow.cpp

#include <iostream>

int shadow(bool cond){
 int d = 0;
 if (cond){
 d = 1;
 }
 else {
 int d = 2;
 d = 3;
 }
 return d;
}

int main(){

 std::cout << std::endl;
 
 std::cout << shadow(true) << std::endl; 
 std::cout << shadow(false) << std::endl; 

 std::cout << std::endl;
 
}

プログラムの出力はどうなりますか? d に混乱していますか?これが結果です。

これは簡単でした!右?しかし、クラス階層では同じ現象が非常に驚くべきものです。

// shadowClass.cpp

#include <iostream>
#include <string>

struct Base{
 void shadow(std::string){ // 2
 std::cout << "Base::shadow" << std::endl; 
 }
};

struct Derived: Base{
 void shadow(int){ // 3
 std::cout << "Derived::shadow" << std::endl; 
 }
};

int main(){
 
 std::cout << std::endl;
 
 Derived derived;
 
 derived.shadow(std::string{}); // 1
 derived.shadow(int{}); 
 
 std::cout << std::endl;
 
}

Base 構造体と Derived 構造体の両方にメソッド シャドウがあります。ベースの 1 つは std::string (2) を受け入れ、もう 1 つは int (3) を受け入れます。デフォルトで構築された std::string (1) で派生したオブジェクトを呼び出すと、ベース バージョンが呼び出されると想定できます。違う!メソッド shadow はクラス Derived に実装されているため、基本クラスのメソッドは名前解決時に考慮されません。これが私の gcc の出力です。

この問題を修正するには、Shadow が Derived に認識されている必要があります。

struct Derived: Base{
 using Base::shadow; // 1
 void shadow(int){
 std::cout << "Derived::shadow" << std::endl; 
 }
};

Base::shadow (1) を Derived に入れる必要があります。これで、プログラムは期待どおりに動作します。

ES.20:常にオブジェクトを初期化する

どのオブジェクトが初期化されるか、または初期化されないかに関する規則は、C++ で正しく理解するのが非常に困難です。以下は簡単な例です。

struct T1 {};
class T2{
 public:
 T2() {} 
};

int n; // OK

int main(){
 int n2; // ERROR
 std::string s; // OK
 T1 t1; // OK
 T2 t2; // OK 
}

n はグローバル変数です。したがって、0 に初期化されます。これは n2 には当てはまりません。これはローカル変数であり、初期化されないためです。ただし、std::string、T1、T2 などのユーザー定義型をローカル スコープで使用すると、それらは初期化されます。

それが難しすぎる場合は、簡単な修正方法があります。自動を使用します。ここで、変数の初期化を忘れることはできません。コンパイラはこれをチェックします。

struct T1 {};
class T2{
 public:
 T2() {}
};

auto n = 0;

int main(){
 auto n2 = 0;
 auto s = ""s; 
 auto t1 = T1(); 
 auto t2 = T2();
}

ES.21:変数を導入しない(または定数) 使用する前に

これは些細なことだと思います。私たちは C ではなく C++ をプログラミングします。

ES.22:宣言しないでください初期化する値が得られるまでの変数

このルールに従わないと、いわゆるセット前の使用エラーが発生する可能性があります。ガイドラインをご覧ください。

int var; 

if (cond) // some non-trivial condition
 Set(&var);
else if (cond2 || !cond3) {
 var = Set2(3.14);
}

// use var

条件の1つが当てはまるかどうか知っていますか?そうでない場合、ローカル組み込み変数としての var が使用されますが、初期化されません。

ES.23:150 を優先 -初期化構文

{} 初期化を使用する理由はたくさんあります:

  • 常に適用
  • 最も厄介な解析を克服
  • 狭くなるのを防ぎます

特別なルールを覚えておく必要があります。 auto を {} 初期化と組み合わせて使用​​すると、C++14 では std::initializer_list が取得されますが、C++17 では取得されません。

詳細については、{}-初期化

に関する以前の投稿を参照してください。

ES.24:163 ポインタを保持する

短くします。 std::unique_ptr は設計上、生のポインターと同じくらい効率的ですが、大きな付加価値があります。リソースを処理します。これは、生のポインターを使用しないことを意味します。 std::unique_ptr の詳細に興味がある場合は、std::unquiue_ptr への私の 2 つの投稿をお読みください。

次は?

C++ での宣言の規則はまだ終わっていません。残りは次の投稿に続きます。