44

私は最近 Scott Meyers から新しいEffective modern C++ を購入し、今それを読んでいます。しかし、私は完全に私を悩ませている 1 つのことに遭遇しました。

項目 5 で、Scott は使用することautoは素晴らしいことだと述べています。入力を節約し、ほとんどの場合に正しい型を提供し、型の不一致の影響を受けない可能性があります。私はこれを完全に理解してautoおり、良いことだとも考えています。

しかし、項目 6 で、スコットは、すべてのコインには 2 つの面があると述べています。auto同様に、たとえばプロキシ オブジェクトの場合など、 が完全に間違った型を推測する場合もあります。

次の例はすでにご存知かもしれません。

class Widget;
std::vector<bool> features(Widget w);

Widget w;

bool priority = features(w)[5]; // this is fine

auto priority = features(w)[5]; // this result in priority being a proxy
                                // to a temporary object, which will result
                                // in undefined behavior on usage after that
                                // line

ここまでは順調ですね。

しかし、これに対するスコットの解決策は、いわゆる「明示的に型付けされた初期化子イディオム」です。アイデアは、次のように初期化子で static_cast を使用することです。

auto priority = static_cast<bool>(features(w)[5]);

しかし、これはより多くの型付けにつながるだけでなく、推論されるべき型を明示的に述べることも意味します。基本的にauto、明示的な特定の型に対する両方の利点を失います。

なぜこのイディオムを使用するのが有利なのか、誰か教えてもらえますか?


最初に物事を明確にするために、私の質問は、なぜ私が書くべきかを目的としています:

auto priority = static_cast<bool>(features(w)[5]);

それ以外の:

bool priority = features(w)[5];

@Sergey は、このトピックに関するGotWの素敵な記事へのリンクを提供してくれました。これは、私の質問に部分的に答えています。

ガイドライン: ローカル変数 auto x = type{ expr }; の宣言を検討してください。タイプに明示的にコミットしたい場合。コードが明示的に変換を要求していること、変数が初期化されることが保証されていること、および偶発的な暗黙的な縮小変換が許可されていないことを示すことは自己文書化されています。明示的な絞り込みが必要な場合にのみ、{ } の代わりに ( ) を使用してください。

これは基本的に関連する質問につながります。これらの4 つの選択肢のうち、どれを選択する必要がありますか?

bool priority = features(w)[5];

auto priority = static_cast<bool>(features(w)[5]);

auto priority = bool(features(w)[5]);

auto priority = bool{features(w)[5]};

ナンバーワンは今でも私のお気に入りです。入力が少なく、他の 3 つと同じくらい明確です。

保証された初期化に関するポイントは、実際には当てはまりません。変数を何らかの方法で初期化する前ではなく、変数を宣言しているためです。狭小化に関するもう 1 つの議論は、簡単なテストではうまくいきませんでした。

4

4 に答える 4

26

C++ 標準に従う:

§ 8.5 初期化子[dcl.init]

  1. フォームで発生する初期化

    T x = a;
    

    引数の受け渡し、関数の戻り、例外のスロー (15.1)、例外の処理 (15.3)、および集約メンバーの初期化 (8.5.1) と同様に、copy-initializationと呼ばれます。

本に書かれている例を考えることができます:

auto x = features(w)[5];

auto / template タイプ (一般的には推定タイプ) を使用した任意の形式のコピー初期化を表すものとして、次のように:

template <typename A>
void foo(A x) {}

foo(features(w)[5]);

としても:

auto bar()
{
    return features(w)[5];
}

としても:

auto lambda = [] (auto x) {};
lambda(features(w)[5]);

つまり、常に「型 Tstatic_cast<T>を代入の左側に移動する」とは限らないということです。

代わりに、上記の例のいずれかで、後者が未定義の動作につながる可能性がある場合、コンパイラが独自に推測できるようにするのではなく、目的の型を明示的に指定する必要があります。

私の例に応じて、次のようになります。

/*1*/ foo(static_cast<bool>(features(w)[5]));

/*2*/ return static_cast<bool>(features(w)[5]);

/*3*/ lambda(static_cast<bool>(features(w)[5]));

そのため、 usingstatic_cast<T>は目的の型を強制する洗練された方法であり、代わりに明示的なコンストラクター呼び出しによって表現することもできます。

foo(bool{features(w)[5]});

要約すると、この本には次のようには書かれていないと思います。

変数の型を強制したいときはいつでも、auto x = static_cast<T>(y);代わりにT x{y};.

私には、警告の言葉のように聞こえます。

の型推論autoはクールですが、使い方を誤ると未定義の動作になる可能性があります。

また、型推論を含むシナリオの解決策として、次のことが提案されています。

コンパイラの通常の型推定メカニズムが必要なものでない場合は、 を使用してくださいstatic_cast<T>(y)


アップデート

そして、あなたの更新された質問に答えて、以下の初期化のどれを好むべきですか:

bool priority = features(w)[5];

auto priority = static_cast<bool>(features(w)[5]);

auto priority = bool(features(w)[5]);

auto priority = bool{features(w)[5]};

シナリオ 1

std::vector<bool>::referenceまず、が暗黙的に に変換できないと想像してboolください。

struct BoolReference
{
    explicit operator bool() { /*...*/ }
};

これは明示的なブール コンテキストではないため、 はコンパイルされbool priority = features(w)[5];ません。他のものは正常に動作します ( にアクセスできる限りoperator bool())。

シナリオ 2

次に、 が古い方法std::vector<bool>::reference実装されていると仮定しましょう。変換演算子は ではありませんが、代わりに次の値を返します。explicitint

struct BoolReference
{
    operator int() { /*...*/ }
};

署名の変更は初期をオフします。auto priority = bool{features(w)[5]};{}intbool

シナリオ 3

bool第三に、まったくではなく、コンストラクターを宣言するユーザー定義型について話しているとしたらどうでしょうか。explicit

struct MyBool
{
    explicit MyBool(bool b) {}
};

驚くべきことに、コピー初期化構文には非明示的なコンストラクターが必要なため、MyBool priority = features(w)[5];初期化は再びコンパイルされません。ただし、他の人は機能します。

個人の態度

リストされた 4 つの候補から 1 つの初期化を選択する場合、次のようにします。

auto priority = bool{features(w)[5]};

明示的なブール値コンテキストを導入し (この値をブール変数に代入したい場合には問題ありません)、ナローイングを防止します (他の型の場合、容易にブール値に変換できません)。そのため、エラー/警告がトリガーされた場合、features(w)[5] 実際に何が何であるかを診断できます。


更新 2

私は最近、Back to the Basics!というタイトルのCppCon 2014での Herb Sutter のスピーチを見ました。Essentials of Modern C++ Styleでは、 よりもフォームの明示的な型初期化子を優先する必要がある理由についていくつかのポイントを示してauto x = T{y};います (ただし、 with と同じではないauto x = static_cast<T>(y)ため、すべての引数が適用されるわけではありません) T x{y};

  1. auto変数は常に初期化する必要があります。つまり、auto a;エラーが発生しやすいように を書くことはできません。int a;

  2. 最新のC++スタイルでは、次のように右側の型が優先されます。

    a) リテラル:

    auto f = 3.14f;
    //           ^ float
    

    b) ユーザー定義リテラル:

    auto s = "foo"s;
    //            ^ std::string
    

    c) 関数宣言:

    auto func(double) -> int;
    

    d) 名前付きラムダ:

    auto func = [=] (double) {};
    

    e) エイリアス:

    using dict = set<string>;
    

    f) テンプレートのエイリアス:

    template <class T>
    using myvec = vector<T, myalloc>;
    

    そのため、もう1つ追加します。

    auto x = T{y};
    

    は、左側に名前があり、右側に初期化子を持つ型があるスタイルと一致しています。これは、次のように簡単に説明できます。

    <category> name = <type> <initializer>;
    
  3. copy-elision および非明示的な copy/move コンストラクターを使用すると、構文に比べてコストがかかりません。T x{y}

  4. タイプ間に微妙な違いがある場合は、より明確になります。

     unique_ptr<Base> p = make_unique<Derived>(); // subtle difference
    
     auto p = unique_ptr<Base>{make_unique<Derived>()}; // explicit and clear
    
  5. {}暗黙的な変換やナローイングがないことを保証します。

しかし、彼はまたauto x = T{}、この投稿ですでに説明されている、一般的な形式のいくつかの欠点についても言及しています。

  1. コンパイラは右側の一時を省略できますが、アクセス可能で、削除されておらず、明示的でないコピー コンストラクタが必要です。

     auto x = std::atomic<int>{}; // fails to compile, copy constructor deleted
    
  2. 省略が有効になっていない場合 (例: -fno-elide-constructors)、移動不可能な型を移動すると、コストのかかるコピーが発生します。

     auto a = std::array<int,50>{};
    
于 2014-09-01T16:18:48.720 に答える
15

目の前に本がないので、これ以上の文脈があるかどうかはわかりません。

しかし、あなたの質問に答えるには、いいえ、この特定の例でauto+を使用することは良い解決策ではありません。static_castこれは別のガイドラインに違反しています (正当化された例外を見たことがありません)。

  • 可能な限り弱いキャスト/変換を使用します。

不必要に強いキャストは、型システムを破壊し、互換性のない方法で変換に影響を与える変更がプログラムの他の場所で発生した場合に、コンパイラが診断メッセージを生成するのを防ぎます。(離れた場所でのアクション、保守プログラミングのブギーマン)

ここでstatic_castは不必要に強いです。暗黙の変換はうまくいきます。だから、キャストを避けてください。

于 2014-09-01T14:43:22.897 に答える
3

なぜこのイディオムを使用するのが有利なのか、誰か教えてもらえますか?

私が考えることができる理由:それは明示的だからです。このコードを(本能的に)どのように読むかを考えてみてください(つまり、何をするかを知らなくてfeaturesも):

bool priority = features(w)[5];

"Features は、いくつかの一般的な "ブール" 値のインデックス可能なシーケンスを返します。5 番目の値をpriority".

auto priority = static_cast<bool>(features(w)[5]);

"Features は、明示的に に変換できるインデックス可能な一連の値を返します。5bool番目の値をpriority".

このコードは、最短の柔軟なコードを最適化するために書かれたものではありませんが、結果の明示性 (および明らかに一貫性 - auto で宣言された唯一の変数ではないと想定しているため) のために書かれています。

の宣言で auto を使用するのpriorityは、右側にある式が何であれ、コードを柔軟に保つためです。

そうは言っても、明示的なキャストのないバージョンを好むでしょう。

于 2014-09-01T14:54:11.360 に答える