web-dev-qa-db-ja.com

C ++は、仮想ベースAを介してベースAから派生タイプBに変換できません

私には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
51

キャストシステムを理解するには、オブジェクトモデルを詳しく調べる必要があります。

単純な階層モデルの古典的な表現は包含です:BAから派生する場合、Bオブジェクトは実際にAサブオブジェクトを含む独自の属性。

このモデルでは、ダウンキャスティングは、Bのメモリレイアウトに依存するコンパイル時に既知のオフセットによる単純なポインター操作です。

これがstatic_cast doです。静的キャストは静的キャストと呼ばれます。これは、キャストに必要なものの計算がコンパイル時に行われるためです(ポインター演算または変換(*))。

しかし、virtual継承が物事を開始すると、少し難しくなる傾向があります。主な問題は、virtual継承により、すべてのサブクラスがサブオブジェクトの同じインスタンスを共有することです。それを行うために、Bは適切なAの代わりにAへのポインターを持ち、A基本クラスオブジェクトがインスタンス化されます。 Bの外側。

したがって、コンパイル時に必要なポインター演算を推測することは不可能です。これは、オブジェクトの実行時の型に依存します。

ランタイムタイプの依存関係がある場合は常に、RTTI(RunTime Type Information)が必要であり、キャストにRTTIを使用するのはdynamic_castの仕事です。

要約すれば:

  • コンパイル時のダウンキャスト:static_cast
  • ランタイムダウンキャスト:dynamic_cast

他の2つはコンパイル時のキャストでもありますが、それらは非常に具体的であるため、それらが何のためにあるのかを思い出すのは簡単です。

(*)@ curiousguyがコメントで述べたように、これはダウンキャストにのみ当てはまります。 static_castは、仮想または単純な継承に関係なくアップキャストを許可しますが、キャストも不要です

89
Matthieu M.

私の知る限り、継承はvirtualであり、ダウンキャストしているため、dynamic_castを使用する必要があります。

12
Jon Purdy

コンパイラーはコンパイル時にAに対するBのオフセットを知らないため、この状況ではstatic_castを使用できません。オフセットは、最も派生したオブジェクトの正確なタイプに基づいて、実行時に計算する必要があります。したがって、dynamic_castを使用する必要があります。

6
ybungalobill

はい、dynamic_castを使用する必要がありますが、ベースクラスをポリモーフィックにする必要があります。仮想dtorを追加します。

4
Nico

標準ドキュメントによると、

セクション5.2.9-9Static Cast

タイプが「cv1 Bへのポインター」の右辺値(Bはクラスタイプ)は、タイプが「cv2 Dへのポインター」の右辺値に変換できます。ここで、有効な標準であれば、DはBから派生したクラス(10節)です「pointer to D」から「pointer to B」への変換が存在する(4.10)、cv2はcv1と同じcv-qualificationまたはそれより大きいcv-qualificationであり、Bはどちらも仮想ベースではないDのクラスでも、Dの仮想基本クラスの基本クラスでもありません。

したがって、それは不可能であり、dynamic_cast...を使用する必要があります.

4
liaK

$ 5.2.9/2-「式eは、宣言「T t(e);」の場合、static_cast(e)の形式のstatic_castを使用して明示的に型Tに変換できます。いくつかの一時変数t(8.5)が発明されたため、整形式です。」

あなたのコードでは、 'T = B *'と 'e = A *'でstatic_castを試みています。

現在、「B * t(A *)」はC++では整形式ではありません(ただし、「A * t(B *)」は、「A」が「B」の仮想の明確でアクセス可能なベースであるためです。 。

1
Chubsdad

これが「安全」かどうかはわかりませんが。

想定

Aから派生したB(および純粋な仮想)

私は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つの等しいクラスで何かをしていることを確認します)あなたは私が「定数」メソッドに自分自身を制限したことがわかります。したがって、これは「非定数」メソッドで動作しますが、何か間違ったことをすると、バグをキャッチすることはほとんど不可能になります。また、アサーションを使用しても、失敗すると想定されていても、アサーションを成功させる可能性は40億のうち1つです(assert(ID == other-> ID);)

ところで..良いOOデザインはこの種のものを必要としないはずですが、私の場合は再解釈キャストの使用を落とすことなくコードをリファクタリング/再設計しようとしました。一般的に言えば、この種のことは避けることができます。

1
GameDeveloper