2015-09-16 5 views
103

私は誤って次のように動作することを発見して驚いています:私は、自身のコンストラクタに構築されたオブジェクトのアドレスを渡しています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... 
} 

。これは、ソースレベルで循環定義のように見えます。標準では、実際にオブジェクトが構築される前に関数にオブジェクトを渡すことができますか、この未定義の動作ですか?

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

私はこれが有用か良い考えであるかどうか質問しません。私は、クラスについてもっと学ぶためにちょうど周りをちょっと調べています。

+1

@ShafikYaghmourなぜ回答を削除しましたか?制限のために[basic.life] p6の引用を追加するだけです。 – dyp

+2

はい、それは問題ありません。基本的にコンストラクタで 'this'を使うのと同じですが、全ての落とし穴があります。 –

+2

これは 'size_t x = sizeof(x)'のようなものではありませんか?オブジェクトのコンストラクタは、メモリが割り当てられている場所(不特定のソースから)で呼び出されます。ストレージのプロパティにのみ依存し、値の解釈に依存しない限り、物事は安全でなければなりません。 – MSalters

答えて

62

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

これは言うどのdefect report 363: Initialization of class from 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 *へのポインタをキャストできなかったことを除いて、これらの例は標準に準拠しています。

同様に、オブジェクトのライフタイムが開始したが後にする前に次のよう

ドラフトC++ 14標準から部3.8[basic.life]の完全な引用は オブジェクトが占有するストレージが割り当てられているか、またはオブジェクトの寿命が終了した後で、オブジェクトが占有したストレージが再利用または解放される前に、 オリジナルオブジェクトを参照するglvalueが使用されます限られた方法。構築または破壊中のオブジェクト については、12.7を参照してください。そうでない場合、そのようなglvalue は、割り当てられたストレージ(3.7.4.2)を参照し、その値に依存しないgl値のプロパティを使用すると、明確に定義されます。

  • 左辺値ツー右辺値の変換(4:場合の動作は未定義と プログラム。1)このようなglvalueに適用され、

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

  • れます仮想基本クラス(8.5.3)への参照または

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

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

私たちが打ち鳴らすでこれをしようとした場合、我々は不吉な警告(see it live)を参照してください。

警告:[-Wuninitialized]

独自の初期化中に使用された場合変数 'fooが' 初期化されていません

producing and indeterminate value from an uninitialized automatic variable is undefined behavior以降有効な警告ですが、この場合は参照をバインドするだけで、不確定な値を生成せず有効なコンストラクタ内の変数のアドレスをとるだけです。一方、following self-initialization example from the draft C++11 standard

int x = x ; 

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

Active issue 453: References may only bind to “valid” objectsも関連しているようですが、まだ開いています。最初に提案された言語は、欠陥報告363と一致する。

+0

ああ、OK :) (どういうわけか、それについては考えておらず、それを当然と考えていました)。しかし、スタンダードには細部が見当たりません。 – dyp

+0

@dyp:実際には正確には指定されていませんが、最初の基本クラスのコンストラクタが実行を開始する前に並べ替えが必要です。あなたはどれだけ前に判断することはできません。 – MSalters

+2

ありがとうShafik!スタックオーバーフローに遭遇する人々、そして正しい目の前に正しい質問を置くサイトの能力は、決して私を驚かせることを止めることはありません! –

15

コンストラクタは、オブジェクトが存在するメモリが割り当てられているところで呼び出される。その時点では、その場所にオブジェクトが存在しません(または場合によっては些細なデストラクタを持つオブジェクト)。さらに、thisポインタはそのメモリを参照し、メモリは適切に整列されています。

メモリが割り当てられており、整列されているため、Fooタイプ(つまりFoo&)の左辺値式を使用して参照することがあります。私たちは何かではないはまだ左辺値から右辺値への変換を持っています。これは、コンストラクタ本体が入力された後にのみ許可されます。

この場合、コードはコンストラクタ本体の内部に&barを印刷しようとします。 bar.memberをここに印刷することも合法です。コンストラクタ本体が入力されているので、Fooオブジェクトが存在し、そのメンバーを読み取ることができます。

これは、私たちに小さな細部を1つ残します。それは名前のルックアップです。 Foo foo(foo)では、最初のfooはスコープ内に名前を導入し、2番目のfooはちょうど宣言された名前を参照します。そのため、int x = xは無効ですが、int x = sizeof(x)が有効です。

+0

しかし、g ++ 4.8で 'int x = x'をコンパイルするときに、何のエラーもありませんでした。無効な文はエラーを吐き出すはずですか? – cbinder

+0

@cbinder:_しかし、残念ながら_できません。 – MSalters

関連する問題