22

私はC++のこの振る舞いに戸惑っています:

struct A {
   virtual void print() const { printf("a\n"); }
};

struct B : public A {
   virtual void print() const { printf("b\n"); }
};

struct C {
   operator B() { return B(); }
};

void print(const A& a) {
   a.print();
}

int main() {
   C c;
   print(c);
}

それで、クイズは、プログラムの出力は何ですか-aまたはb?さて、答えはです。しかし、なぜ?

4

1 に答える 1

10

ここでの問題は、C ++ 03標準のバグ/誤動作/穴であり、さまざまなコンパイラがさまざまな方法で問題にパッチを適用しようとしています。(この問題はC ++ 11標準には存在しなくなりました。)

両方の標準のセクション8.5.3/5は、参照の初期化方法を指定しています。これがC++03バージョンです(リストの番号付けは私のものです):

タイプへの参照は、次のようcv1 T1にタイプの式によって初期化さcv2 T2れます。

  1. 初期化式の場合

    1. は左辺値(ただしビットフィールドではありません)であり、「cv1T1」は「cv2T2」と参照互換性があります。
    2. クラスタイプ(つまり、T2クラスタイプ)を持ち、タイプの左辺値に暗黙的に変換できますcv3 T3。ここで、cv1 T1は参照互換です。cv3 T3

    次に、最初のケースでは参照が初期化子式の左辺値に直接バインドされ、2番目のケースでは参照が変換の左辺値の結果にバインドされます。

  2. それ以外の場合、参照は不揮発性constタイプになります(つまり、にcv1なりますconst)。

  3. イニシャライザ式がT2クラスタイプの右辺値であり、とのcv1 T1参照互換性がcv2 T2ある場合、参照は次のいずれかの方法でバインドされます(選択は実装定義です)。

    1. 参照は、右辺値(3.10を参照)で表されるオブジェクトまたはそのオブジェクト内のサブオブジェクトにバインドされます。
    2. タイプcv1 T2[sic]の一時オブジェクトが作成され、コンストラクターが呼び出されて、右辺値オブジェクト全体が一時オブジェクトにコピーされます。参照は、一時または一時内のサブオブジェクトにバインドされます。

    コピーを作成するために使用されるコンストラクターは、コピーが実際に実行されるかどうかに関係なく呼び出し可能でなければなりません。

  4. cv1 T1それ以外の場合は、非参照コピー初期化(8.5)のルールを使用して、初期化子式からタイプの一時オブジェクトが作成および初期化されます。次に、参照は一時的なものにバインドされます。

手元の質問に関係する3つのタイプがあります:

  • 作成する参照のタイプ。標準(両方のバージョン)は、このタイプをとして示しT1ます。この場合はですstruct A
  • 初期化式のタイプ。規格では、このタイプをとして示していT2ます。この場合、初期化式は変数cであり、 。も同様T2ですstruct Cとの参照互換性struct Aないため、参照をに直接バインドすることはできないことに注意してください。中間体が必要です。struct Cc
  • 中間体のタイプ。規格では、このタイプをとして示していT3ます。この場合、これはstruct Bです。に変換演算子C::operator B()を適用cすると、左辺値cが右辺値に変換されることに注意してください。

struct Aは参照と互換性がないため、1.1および3とラベル付けしたものによる初期化は終了していstruct Cます。変換演算子C::operator B()を使用する必要があります。1.2がアウトですこの変換演算子は右辺値を返すため、これは1.2を除外します。残っているのはオプション4だけで、タイプの一時を作成しますcv1 T1。2003バージョンの標準に厳密に準拠すると、1つだけで十分ですが、この問題に対して2つの一時的なものを作成する必要があります。

標準の2011バージョンは、オプション3を次のように置き換えることで問題を修正します。

  • 初期化式の場合

    • xvalue、class prvalue、array prvalue、またはfunction lvalueであり、、、またはと参照cv1 T1互換性があります。cv2 T2
    • はクラスタイプ(つまり、T2クラスタイプ)を持ち、T1はに参照関連ではなく、タイプT2のx値、クラスprvalue、または関数左辺値に暗黙的に変換できますcv3 T3。ここで、は、cv1 T1と参照互換性がありますcv3 T3

    次に、参照は、最初のケースでは初期化式の値にバインドされ、2番目のケースでは変換の結果にバインドされます(または、いずれの場合も、適切な基本クラスのサブオブジェクトにバインドされます)。2番目のケースでは、参照が右辺値参照であり、ユーザー定義の変換シーケンスの2番目の標準変換シーケンスに左辺値から右辺値への変換が含まれている場合、プログラムの形式が正しくありません。

gccファミリーのコンパイラーはインテントよりも厳密なコンプライアンスを選択したようです(不要な一時的なものを作成しないようにします)が、「b」を出力する他のコンパイラーはインテント/標準への修正を選択しました。厳格なコンプライアンスを選択することは必ずしも称賛に値するわけではありません。2003バージョンの標準(たとえば)には、他にもバグや機能の誤りがstd::setあり、gccファミリは厳密なコンプライアンスよりも健全性を選択しました。

于 2013-01-13T04:47:51.490 に答える