2011-01-02 5 views
5

私はかなり長い間C++で複数の継承を使用してきましたが、今日では、サブクラスの1つとして参照するときにポインタアドレスが異なる可能性があることを暗示しています。例えばサブクラスのキャストとポインタのアドレスの変更

Iしている場合、:コンストラクタ上に印刷されたとき

class ClassA{ 
    public: 
     int x; 
     int y; 
     ClassA(){ 
      cout << "ClassA : " << (unsigned int)this << endl; 
     } 
    }; 

    class ClassC{ 
    public: 
     int cc; 
     int xx; 
     ClassC(){ 
      cout << "ClassC : " << (unsigned int)this << endl; 
     } 
    }; 

    class ClassB : public ClassC, public ClassA{ 
    public: 
     int z; 
     int v; 
     ClassB(){ 
      cout << "ClassB : " << (unsigned int)this << endl; 
     } 
    }; 


    int main(){ 

    ClassB * b = new ClassB(); 

    } 

クラスA及びクラスCは異なるアドレスを有しています。

しかし、私はお互いに戻ってそれらをキャストしようとすると、それだけで自動的に動作します:

ClassA * the_a = (ClassA*)b; 
cout << "The A, casted : " << (unsigned int)the_a << endl; 

ClassB * the_b = (ClassB*)the_a; 
cout << "The B, casted back : " << (unsigned int)the_b << endl; 

が、私はこの種の情報は、コードからコンパイラによって導くことができると仮定し、それは安全ですこれはすべてのコンパイラで動作するとしますか?

追加の質問:サブクラスの場所の順序を強制できますか?たとえば、classAがサブクラス化されているClassCと最初に(基本的に同じポインタの場所を共有する)必要がある場合は、最初にサブクラスの宣言に入れる必要がありますか? 更新さて、注文を強制することはできないようです。スーパークラスのレベルで、構造の "ルート"アドレス、サブクラスに割り当てられたアドレスの開始を見つけることはまだ可能ですか?たとえば、ClassBのアドレスをClassAから取得します。

+0

あなたは_ "ルート" _アドレスを取得できるとは思いません。私はそれについて別の質問をするだろう。標準からのAFAICTでは、基本クラスのメンバーまたはサブオブジェクトがより低いメモリアドレスに配置されることさえ保証されていません。言い換えれば、 'the_a'が' the_b'やそれ以外のものより低いアドレスを持つという保証はありません。 –

答えて

6

これは完全継承の標準的な使用方法であり、互換性のあるコンパイラでも動作します。しかし、最初のケースでは明示的なキャストは不要であり、 'Cスタイルのキャスト'は2番目のケースでstatic_castに置き換えることができます。

サブクラスの場所の順序を強制することは可能ですか?

いいえ:レイアウトは実装定義であり、コードはこの注文に依存してはなりません。

+0

私は参照してください。スーパークラス(クラスA)が構造全体のルートアドレスを取得する可能性のある方法はありますか(クラスAのアドレスならばクラスAのアドレス、クラスBの場合はクラスBのアドレス)。私はルートアドレスに依存するメモリプールの実装を構築していました。 – kamziro

+0

@ kamziro:実際には、私はそこにいるとは思わないが、私はあなたが必要でさえ驚いていると言わなければならない。 – icecrime

+0

私は見る。サブクラス化による私のメモリプールの実装は、フリーアドレスをプールに戻す必要があり、ベースアドレスを保存する必要がなく、4バイトを節約できました。 4つの控えめなバイト..私は推測する選択肢があまりにも悪いと思います:) – kamziro

3

はい、これは安全であると思われます。 C++のポインタ型キャストは、ポインタを基底から派生またはその逆の変換のために正しく調整することが保証されています。

つまり、システムをあまりにも遠くに押し込まないように注意する必要があります。ボイド*を通してまとめ、及びそのポインタはBのオブジェクトの内部にある場所に関する情報があり得るためにCからのキャストので、これは壊れ

ClassB* b = new ClassB; 
ClassC* c = (ClassC*)(void*)b; 

:たとえば、コンパイラは、この変換の権利を取得することはありません失われた

ストレートキャストが機能しない別のケースは、仮想継承です。派生クラスから仮想ベースへ、またはその逆にキャストしたい場合は、dynamic_cast演算子を使用してキャストが成功するようにする必要があると思います。

+0

訂正ありがとう!これはまれにしか起こらないので、私はそれを試してみる機会はあまりありませんでした。 – templatetypedef

+0

仮想的な継承に関する良い呼び出しです。これは難題です。私はあなたが 'dynamic_cast'を使用しなければならなかったことに気づいていませんでした。ベースから派生クラスへのキャストの際に継承が非仮想であることが明示的に要求されているC++ 03§5.2.9パラグラフ5および8を参照してください。 –

1

実際、仮想ベースの単純な実装では、派生オブジェクトの既知の場所に仮想ベースのサブオブジェクトへのポインタを配置します。

少し高価な仮想ベースが複数ある場合は、より良い表現(私が考えるマイクロソフトの特許)は、自己相対オフセットを使用しています。これらは各サブオブジェクトに対して不変である(つまり、オブジェクトのアドレスに依存しない)ため、静的メモリに一度格納することができ、必要なのはそれぞれのサブオブジェクトのポインタだけです。

このようなデータ構造がないと、クロスキャストが機能しません。 Bサブオブジェクトが表示されていなくても、仮想ベースAから仮想ベースA内の仮想ベースBにクロスキャストすることができます(正しくリコールすると、2つのベースには共有仮想ベースXがあります)。それはあなたがそれについて考えるとき、かなり毛深いナビゲーションです:ナビゲーションは、最も派生したクラス(AとBの両方を見ることができる)の形状記述子を介して行きます。

このように表現された構造は、「標準の法律で」構造と破壊の間に動的に変更する必要があるため、複雑なオブジェクトの構築や破壊が遅いのはなぜですか。しかし、いったん作成されると、メソッドコール、クロスコールさえも非常に高速です。

関連する問題