$4.11/2 州 -
B
「型 cv のメンバーへのポインター」型の右辺値 ( はクラス型) は、「型cvのメンバーへのポインター」 型 の右辺値に変換できます。ここで、は の派生クラス (節 10) です。がアクセス不能 (節 11)、あいまい (10.2) または仮想 (10.1) の基底クラスである 場合、この変換を必要とするプログラムは形式が正しくありません。T
B
D
T
D
B
B
D
B
私の質問は、仮想基底クラスではないという制限があるのはなぜD
ですか?
$4.11/2 州 -
B
「型 cv のメンバーへのポインター」型の右辺値 ( はクラス型) は、「型cvのメンバーへのポインター」 型 の右辺値に変換できます。ここで、は の派生クラス (節 10) です。がアクセス不能 (節 11)、あいまい (10.2) または仮想 (10.1) の基底クラスである 場合、この変換を必要とするプログラムは形式が正しくありません。T
B
D
T
D
B
B
D
B
私の質問は、仮想基底クラスではないという制限があるのはなぜD
ですか?
非仮想基本クラスが関係する状況を考えてみましょう。
class A { int a; }
class B : public A { int b; }
class C : public A { int c; }
class D : public B, public C { int d; }
考えられるメモリ レイアウトは次のとおりです。
+-------------+
| A: int a; |
+-------------+
| B: int b; |
+-------------+
| A: int a; |
+-------------+
| C: int c; |
+-------------+
| D: int d; |
+-------------+
D
はandA
から継承し、両方ともサブオブジェクトを持っているため、最終的に 2 つのサブオブジェクトになります。B
C
A
メンバー変数へのポインターは、通常、オブジェクトの先頭からの整数オフセットとして実装されます。この場合、オブジェクトの整数オフセットint a
はA
ゼロです。したがって、「int a
型へのポインタA
」は、単にゼロの整数オフセットである可能性があります。
「型へのポインター」を「int a
型へのポインター」に変換するには、 (最初のサブオブジェクト) にあるサブオブジェクトへの整数オフセットが必要です。A
int a
B
A
B
A
「型へのポインター」を「int a
型へのポインター」に変換するには、 (2 番目のサブオブジェクト) にあるサブオブジェクトへの整数オフセットが必要です。A
int a
C
A
C
A
B
コンパイラはと がどこにC
あるかを知っているので、コンパイラはからまたはA
にダウンキャストする方法について十分な情報を持っています。A
B
C
ここで、仮想基本クラスが関係する状況を考えてみましょう:
struct A { int a; }
struct B : virtual public A { int b; }
struct C : virtual public A { int c; }
struct D : public B, public C { int d; }
可能なメモリ レイアウト:
+-------------+
| B: ptr to A | ---+
| int b; | |
+-------------+ |
| C: ptr to A | ---+
| int c; | |
+-------------+ |
| D: int d; | |
+-------------+ |
| A: int a; | <--+
+-------------+
仮想基本クラスは通常、B
とC
(から仮想的に派生するA
) に単一のサブオブジェクトへのポインタを含めることによって実装されA
ます。A
サブオブジェクトへのポインタが必要なのは、 とA
に対する相対位置が一定B
ではないためです。C
int a
「型へのポインタ」しかない場合、とサブオブジェクトの位置が に対して異なる可能性があるため、それを「型A
へのポインタ」にキャストすることはできません。にはnorへのバックポインターがないため、ダウンキャストが機能するのに十分な情報がありません。int a
B
B
C
A
A
B
C
非仮想継承を使用すると、基本クラスと派生クラスのメンバーを、基本クラスを最初にメモリ内に連続して配置できるため、各基本クラスのメンバーは、オブジェクトのアドレスを基準にして同じ場所に配置されます。B
またはですD
。これにより、pointer-to-member-of-B
を pointer -to-member-of- に簡単に変換できますD
。どちらも、オブジェクトのアドレスからのオフセットとして表すことができます。
仮想継承では、派生オブジェクト内のポインター (または同等のもの) を介して基本クラスのメンバーにアクセスし、基本クラスの場所を示す必要があります。これには、この間接化が必要であることを示すために、メンバーへのポインター表現に追加情報を追加する必要があり、メンバーへのポインターを使用する場合は実行時チェックが必要になります。
多くの C++ の背後にある一般原則は、可能な限り実行時のオーバーヘッドを回避することです。この場合、非常に一般的な操作に対する実行時チェックと、非常にあいまいな変換を許可しないという選択がありましたが、そのプリンシパルがここで適用されたようです。
本当に興味深い質問です。今日新しいことを学びました。これは、件名に関連して私が見つけたものです: 派生クラスから仮想基底クラスへのメンバー関数ポインターのキャストは機能しません