web-dev-qa-db-ja.com

コンストラクタとデストラクタでthis->を呼び出すのが安全な場合

これまでのところ、これに対する決定的な答えを見つけることができませんでした。オブジェクト内からthis->を呼び出すのが安全なのはいつですか。特に、コンストラクタとデストラクタの内部から。

また、パブリック継承を使用する場合。この呼び出しの結果にアップキャストとダウンキャストを使用しても安全ですか?

たとえば、次のとおりです。

class foo
{
   foo():
   a(),
   b(this->a)//case 1
   {
       this-> a = 5; //case 2
   }

   int a;
   int b;
};

class bar: public baz
{
   bar():
   baz(this)//case 3 - assuming baz has a valid constructor
   {


   }

}

そして最後に最もありそうもないもの

foo()
   {
      if(static_cast<bar*>(this));//case 4
   }

上記のどのケースが合法ですか?

注:上記のプラクティスの多くはお勧めできません。

32
laurisvr

非静的メンバー関数内では、thisは関数が呼び出されたオブジェクトを指します。有効なオブジェクトである限り、使用しても安全です。

コンストラクターまたはデストラクターの本体内には、現在構築中のクラスの有効なオブジェクトがあります。ただし、これが何らかの派生クラスのベースサブオブジェクトである場合、その時点ではベースサブオブジェクトのみが有効です。そのため、一般的にダウンキャストして派生クラスのメンバーにアクセスしようとすることは安全ではありません。同じ理由で、ここでは仮想関数を注意深く呼び出す必要があります。なぜなら、それらは最終的なオーバーライドではなく、作成または破棄されるクラスに従ってディスパッチされるからです。

コンストラクターの初期化リスト内では、初期化されたメンバーにのみアクセスするよう注意する必要があります。つまり、現在初期化されているメンバーの前に宣言されたメンバー。

基本サブオブジェクトは常に最初に初期化されるため、基本クラスへのアップキャストは常に安全です。

質問に追加したばかりの特定の例について:

  • aがその時点で初期化されているので、ケース1は(もし壊れやすいなら)大丈夫です。 abの後に初期化されるため、bの値でaを初期化することは未定義です。
  • ケース2は問題ありません。その時点ですべてのメンバーが初期化されています。
  • 適切なfooコンストラクターがないため、ケース3はコンパイルされません。存在する場合は、コンストラクターがそれを使用して何をしたかによって異なります。初期化される前にメンバーにアクセスしようとしたかどうかです。
  • ケース4は、欠落している)を追加した場合は整形式ですが、オブジェクトにアクセスするためにポインターを使用しようとした場合は危険です。 thisはまだ有効なbarオブジェクトを指していません(foo部分のみが初期化されているため)barのメンバーにアクセスすると未定義の動作が発生する可能性があります。ポインターが非ヌルであるかどうかを単純にチェックするだけで十分であり、常にtrueを返します(無意味なキャストを適用するかどうか)。
34
Mike Seymour

C++ super-faqには適切なエントリがあります。

https://isocpp.org/wiki/faq/ctors#using-this-in-ctors

オブジェクトがまだ完全に形成されていないため、コンストラクターでthisポインターを使用すべきではないと感じる人もいます。ただし、注意が必要な場合は、コンストラクター({body}および初期化リスト)でこれを使用できます。

常に機能するものがあります:コンストラクター(またはコンストラクターから呼び出される関数)の{body}は、基本クラスで宣言されたデータメンバーおよび/またはコンストラクター自身のクラスで宣言されたデータメンバーに確実にアクセスできます。これは、コンストラクターの{body}が実行を開始するまでに、これらすべてのデータメンバーが完全に構築されていることが保証されるためです。

コンストラクター(またはコンストラクターから呼び出される関数)の{body}は、派生クラスでオーバーライドされる仮想メンバー関数を呼び出して派生クラスに到達することはできません。派生クラスのオーバーライドされた関数に到達することが目標だった場合、望みどおりの結果は得られません。仮想メンバー関数の呼び出し方法に関係なく、派生クラスのオーバーライドに到達しないことに注意してください。明示的にthisポインター(this-> method())を使用して、暗黙的にthisポインター(method( ))、またはthisオブジェクトの仮想メンバー関数を呼び出す他の関数を呼び出します。一番下の行はこれです:呼び出し元が派生クラスのオブジェクトを構築している場合でも、基本クラスのコンストラクター中に、オブジェクトはまだその派生クラスのものではありません。あなたは警告されました。

時々機能するものがあります。このオブジェクトのデータメンバーを別のデータメンバーの初期化子に渡す場合、他のデータメンバーが既に初期化されていることを確認する必要があります。良いニュースは、使用している特定のコンパイラに依存しない簡単な言語規則を使用して、他のデータメンバーが初期化されているかどうかを判断できることです。悪いニュースは、これらの言語規則を知っておく必要があることです(たとえば、基本クラスのサブオブジェクトが最初に初期化され(複数および/または仮想継承がある場合は順序を調べてください!)、クラスで定義されたデータメンバーはクラス宣言に現れる順序)。これらのルールがわからない場合は、このオブジェクトのデータメンバーを(このキーワードを明示的に使用するかどうかに関係なく)他のデータメンバーの初期化子に渡さないでください。ルールを知っている場合は、注意してください。

19
Goz

Thisポインターは、すべての非静的メンバー関数でアクセスできます...

§9.3.2/ 1

非静的(9.3)メンバー関数の本体では、キーワードthisは値が関数が呼び出されるオブジェクトのアドレスであるprvalue式です。クラスXのメンバー関数のこの型はX *です。メンバー関数がconstと宣言されている場合、これの型はconst X *、メンバー関数がvolatileと宣言されている場合、thisの型はvolatile X *、メンバー関数がconst volatileと宣言されている場合、この型はconst揮発性X *。

...コンストラクタとデストラクタはメンバー関数です...

§12/ 1

デフォルトのコンストラクター(12.1)、コンストラクターのコピーと代入演算子のコピー(12.8)、コンストラクターの移動と代入演算子の移動(12.8)、およびデストラクター(12.4)は特別なメンバー関数です。

...静的ではありません。

§12.1/ 4

コンストラクターは、仮想(10.3)または静的(9.4)であってはなりません。

§12.4/ 2

デストラクタは静的であってはなりません。

したがって、thisはコンストラクタとデストラクタで使用できます。ただし、制限があります(特に初期化子リスト内でのthisの使用に関して)。

(注:コンストラクター/デストラクタ本体内で、すべてのサブオブジェクトとメンバーの初期化が完了し、それらにアクセスできます。以下を参照)。

1。 thisを介して、構築中のオブジェクト(またはそのサブオブジェクト)のみにアクセスします。

§12.1/ 14

Constオブジェクトの構築中に、コンストラクターのthisポインターから直接または間接的に取得されないglvalueを介してオブジェクトまたはそのサブオブジェクトの値にアクセスする場合、オブジェクトまたはサブオブジェクトの値このようにして得られたものは不特定です。

2。基本コンストラクターの派生クラスでオーバーライドされる仮想関数を呼び出さないでください

§12.7/ 4

仮想関数(10.3)を含むメンバー関数は、構築中または破棄中に呼び出すことができます(12.6.2)。クラスの非静的データメンバーの構築中または破棄中を含め、コンストラクターまたはデストラクタから仮想関数が直接または間接的に呼び出され、呼び出しが適用されるオブジェクトが構築中のオブジェクト(xと呼ぶ)である場合または破棄、呼び出される関数は、コンストラクターまたはデストラクターのクラスの最後のオーバーライドであり、より派生したクラスでオーバーライドするものではありません。仮想関数呼び出しが明示的なクラスメンバーアクセス(5.2.5)を使用し、オブジェクト式がxまたはそのオブジェクトの基本クラスサブオブジェクトの完全なオブジェクトを参照し、xまたはその基本クラスサブオブジェクトのいずれかを参照しない場合、動作は未定義です。

3。 dynamic_castを適用して、thisを構築中の型またはその基本型以外の型にキャストしないでください。

§12.7/ 6

dynamic_casts(5.2.7)は、構築中または破棄中に使用できます(12.6.2)。 dynamic_castがコンストラクター(非静的データメンバーのmem-initializerまたはbrace-or-equal-initializerを含む)またはデストラクタで使用される場合、またはコンストラクターから(直接または間接的に)呼び出される関数で使用される場合、またはデストラクター。dynamic_castのオペランドが構築中または破棄中のオブジェクトを参照する場合、このオブジェクトは、コンストラクターまたはデストラクターのクラスのタイプを持つ最も派生したオブジェクトと見なされます。 dynamic_castのオペランドが構築中または破棄中のオブジェクトを参照し、オペランドの静的型がコンストラクターまたはデストラクタ自身のクラスまたはそのベースのポインタまたはオブジェクトではない場合、dynamic_castは未定義の動作になります。

4。 thisのベースタイプポインターへの変換は、構築されたベースタイプで構成されるパスを介してのみ許可されます。

§12.7/ 3

クラスXのオブジェクトを参照するポインター(glvalue)をXの直接または間接ベースクラスBへのポインター(参照)に明示的または暗黙的に変換するには、Xの構築とその直接または間接ベースのすべての構築Bから直接または間接的に派生したものが開始され、これらのクラスの破壊が完了していない場合、変換は未定義の動作をもたらします。オブジェクトobjの直接の非静的メンバーへのポインターを形成する(または値にアクセスする)ために、objの構築が開始され、その破壊が完了していない、そうでなければポインター値の計算(またはメンバーへのアクセス)値)未定義の動作になります。

初期化子リストおよびコンストラクター本体のサブオブジェクトとメンバーへのアクセス

原則として、アクセスする前に初期化が行われる場合、初期化リストから構築/初期化されたオブジェクトにアクセスできます。初期化の順序は

§12.6.2/ 10

非委任コンストラクターでは、初期化は次の順序で進行します。

  • まず、最も派生したクラス(1.8)のコンストラクターに対してのみ、仮想基底クラスは、基底クラスの有向非巡回グラフの深さ優先左から右へのトラバースに現れる順序で初期化されます。 to-right」は、派生クラスのbase-specifier-listでの基本クラスの出現順序です。

  • 次に、直接の基本クラスは、(mem-initializerの順序に関係なく)base-specifier-listに表示される宣言順に初期化されます。

  • 次に、非静的データメンバは、クラス定義で宣言された順序で初期化されます(これもmem-initializerの順序に関係なく)。

  • 最後に、コンストラクター本体の複合ステートメントが実行されます。

7
Pixelchemist