web-dev-qa-db-ja.com

C ++オブジェクトを独自のコンストラクターに渡すことは合法ですか?

次の機能が偶然発見されたことに驚いた。

#include <iostream>            
int main(int argc, char** argv)
{
  struct Foo {
    Foo(Foo& bar) {
      std::cout << &bar << std::endl;
    }
  };
  Foo foo(foo); // I can't believe this works...
  std::cout << &foo << std::endl; // but it does...
}

構築されたオブジェクトのアドレスを独自のコンストラクターに渡します。これは、ソースレベルでの循環定義のように見えます。標準は、オブジェクトが構築される前にオブジェクトを関数に渡すことを本当に許可していますか、またはこの未定義の動作ですか?

すべてのクラスメンバー関数が、クラスインスタンスのデータへのポインターを暗黙的なパラメーターとして既に持っていることを考えると、それほど奇妙ではないと思います。また、データメンバーのレイアウトはコンパイル時に修正されます。

注、これが役に立つのか良いアイデアなのかを尋ねているわけではありません。クラスについてもっと知りたいと思っています。

109
Andrew Wagner

これは未定義の動作ではありません。 fooは初期化されていませんが、標準で許可されている方法で使用しています。オブジェクトにスペースが割り当てられた後、完全に初期化される前に、限られた方法で使用することができます。その変数への参照のバインドとそのアドレスの取得の両方が許可されます。

これは 障害レポート363:selfからのクラスの初期化 でカバーされています。

もしそうなら、UDTの自己初期化のセマンティクスは何ですか?例えば

 #include <stdio.h>

 struct A {
        A()           { printf("A::A() %p\n",            this);     }
        A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
        ~A()          { printf("A::~A() %p\n",           this);     }
 };

 int main()
 {
  A a=a;
 }

コンパイルして印刷できます:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

解像度は:

3.8 [basic.life]パラグラフ6は、ここでの参照が有効であることを示しています。完全に初期化される前にクラスオブジェクトのアドレスを取得することは許可されており、参照が直接バインドできる限り、参照パラメーターへの引数として渡すことが許可されています。 printfs内の%pのポインターをvoid *にキャストできないことを除いて、これらの例は標準に準拠しています。

セクションの完全な引用符3.8[basic.life]C++ 14標準ドラフトからの抜粋は次のとおりです。

同様に、オブジェクトのライフタイムが始まる前、オブジェクトが占有するストレージが割り当てられた後、またはオブジェクトのライフタイムが終了し、オブジェクトが占有したストレージが再利用または解放される前に、参照するglvalue元のオブジェクトは使用できますが、限られた方法でのみ使用できます。建設中または破壊中のオブジェクトについては、12.7を参照してください。それ以外の場合、そのようなglvalueは割り当てられたストレージ(3.7.4.2)を指し、その値に依存しないglvalueのプロパティの使用は明確に定義されています。次の場合、プログラムの動作は未定義です。

  • このようなglvalueに左辺値から右辺値への変換(4.1)が適用されます。

  • glvalueを使用して、非静的データメンバーにアクセスしたり、オブジェクトの非静的メンバー関数を呼び出したり、または

  • glvalueが仮想ベースクラス(8.5.3)への参照にバインドされている、または

  • glvalueはdynamic_cast(5.2.7)のオペランドまたはtypeidのオペランドとして使用されます。

上記の箇条書きで定義されている未定義の動作に該当するfooについては何もしていません。

Clangでこれを試すと、不吉な警告が表示されます(ライブで見る):

警告:変数 'foo'は、独自の初期化内で使用されると初期化されません[-Wuninitialized]

初期化されていない自動変数から不定値を生成することは未定義の動作です なので、これは有効な警告です。ただし、この場合は、参照をバインドし、コンストラクター内で変数のアドレスを取得するだけです。これは、不定値を生成せず、有効です。一方、 ドラフトC++ 11標準の自己初期化の例に続く

int x = x ;

未定義の動作を呼び出します。

アクティブな問題453:参照は「有効な」オブジェクトにのみバインドできます も関連しているようですが、まだ開いています。最初に提案された言語は、障害レポート363と一致しています。

65
Shafik Yaghmour

コンストラクターは、メモリが対象オブジェクトに割り当てられた時点で呼び出されます。その時点では、その場所にオブジェクトは存在しません(または、ささいなデストラクタを持つオブジェクト)。さらに、thisポインターはそのメモリを参照し、メモリは適切にアライメントされます。

メモリは割り当てられて整列されているため、Foo型の左辺値式(つまり_Foo&_)を使用して参照できます。 notまだできることは、左辺値から右辺値への変換です。これは、コンストラクター本体が入力された後にのみ許可されます。

この場合、コードはコンストラクタ本体内で_&bar_を出力しようとします。ここで_bar.member_を出力することもできます。コンストラクター本体が入力されているため、Fooオブジェクトが存在し、そのメンバーが読み取られます。

これにより、詳細が1つだけ残ります。これが名前の検索です。 Foo foo(foo)では、最初のfooがスコープ内の名前を紹介するため、2番目のfooは宣言された名前を参照します。 _int x = x_は無効ですが、int x = sizeof(x)は有効です。

15
MSalters