私は3つのクラスを持っています:
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D: public B, public C {};
A* から B* への静的キャストを試行すると、次のエラーが発生します。
cannot convert from base A to derived type B via virtual base A
私は3つのクラスを持っています:
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D: public B, public C {};
A* から B* への静的キャストを試行すると、次のエラーが発生します。
cannot convert from base A to derived type B via virtual base A
キャスト システムを理解するには、オブジェクト モデルに飛び込む必要があります。
単純な階層モデルの古典的な表現は包含です。オブジェクトがB
派生しA
た場合B
、実際には、A
オブジェクトはそれ自体の属性とともにサブオブジェクトを含みます。
このモデルでは、ダウンキャストはコンパイル時に既知のオフセットによる単純なポインター操作であり、これは のメモリ レイアウトに依存しますB
。
これはstatic_castが行うことです: 静的キャストは静的と呼ばれます。これは、キャストに必要なものの計算がコンパイル時に行われるためです (ポインター演算または変換 (*))。
ただし、virtual
継承が始まると、状況は少し難しくなる傾向があります。主な問題は、virtual
継承により、すべてのサブクラスがサブオブジェクトの同じインスタンスを共有することです。そのために、は適切なオブジェクトではなくB
へのポインタを持ち、基本クラス オブジェクトは の外部でインスタンス化されます。A
A
A
B
したがって、コンパイル時に必要なポインター演算を推測することは不可能です。これは、オブジェクトの実行時の型に依存します。
ランタイム型の依存関係がある場合は常に RTTI (RunTime Type Information) が必要であり、キャストに RTTI を利用するのはdynamic_castの仕事です。
要約すれば:
static_cast
dynamic_cast
他の 2 つもコンパイル時のキャストですが、非常に具体的であるため、目的を簡単に覚えることができます...そして臭いので、とにかく使用しない方がよいでしょう。
(*)コメントで @curiousguy が指摘したように、これはダウンキャストにのみ適用されます。Astatic_cast
は、仮想継承または単純継承に関係なくアップキャストを許可しますが、キャストも不要です。
私の知る限りdynamic_cast
、継承がvirtual
あり、ダウンキャストしているため、使用する必要があります。
You can't use static_cast
in this situation because the compiler doesn't know the offset of B relative to A at compile time. The offset must be calculated at run-time based on the exact type of the most derived object. Therefore you must use dynamic_cast
.
標準ドキュメントによると、
セクション5.2.9 - 9、静的キャストについては、
タイプ「cv1 B へのポインター」の右辺値 (B はクラス タイプ) は、タイプ「cv2 D へのポインター」の右辺値に変換できます。ここで、D は B から派生したクラス (第 10 節) です。 「D へのポインター」から「B へのポインター」への変換が存在し (4.10)、cv2 が cv1 と同じ cv 修飾であるか、cv1 より大きい cv 修飾であり、B が D の仮想基底クラスでも基底クラスでもないD の仮想基底クラスの
したがって、それは不可能であり、使用する必要がありますdynamic_cast
...
はい、dynamic_cast を使用する必要がありますが、仮想 dtor を追加するなどして、基本クラス A をポリモーフィックにする必要があります。
これが「安全」かどうかはわかりませんが。
仮定
A から派生した B (および A 純粋仮想)
BへのポインターはまだBへのポインターのままであることを知っているので。
class A
{
virtual void doSomething(const void* p) const =0;
};
class B
{
public:
int value;
virtual void doSomething(const void*p)const
{
const B * other = reinterpret_cast<const B*>(p);
cout<<"hello!"<< other->value <<endl;
}
};
int main()
{
B foo(1),bar(2);
A * p = &foo, q=&bar;
p->doSomething(q);
return 0;
}
このプログラムは実行され、正しく「hello!」という印刷を返します。もう一方のオブジェクトの値 (この場合は「2」)。
ところで、私がやっていることは非常に安全ではありません (個人的には、すべてのクラスに異なる ID を与え、キャストを再解釈した後、現在の ID が他の ID と等しいことを確認して、2 つの等しいクラスで何かを行っていることを確認します)。ご覧のとおり、私は「const」メソッドに限定していました。したがって、これは「非定数」メソッドで機能しますが、何か間違ったことをすると、バグをキャッチすることはほとんど不可能になります。また、アサーションがあっても、40 億回に 1 回、失敗するはずのアサーションが成功する可能性があります (assert(ID== other->ID);)。
ところで..良いオブジェクト指向設計では、この種のものは必要ありませんが、私の場合、再解釈キャストの使用をやめることができずに、コードをリファクタリング/再設計しようとしました。一般的に言えば、この種のことは避けることができます。
$5.2.9/2- 「式 e は、宣言「T t(e);」いくつかの発明された一時変数 t (8.5) の場合、整形式です。」
あなたのコードでは、'T = B*' と 'e = A*' で static_cast を試みています。
現在、'B* t(A*)' は C++ では整形式ではありません (ただし、'A* t(B*)' は、'A' が仮想の明確でアクセス可能な 'B' のベースであるためです。したがって、コードはエラーを返します.