3

次のコードがあるとします。

namespace Example1 {

class A {
public:
    A() {}
    virtual ~A() {}
private:
    float data_A;
};

class B {
public:
    B() {}
    virtual ~B() {}
protected:
    float data_B;
};

class Derived : public A, public B {
public:
    Derived() {}
    virtual ~Derived() {}
protected:
    float data_Derived;
};

}

int main (void)
{
using namespace Example1;
B* pb = new Derived;
delete pb;
}

pbBオブジェクトの一部を指している必要がありDerivedます。しかし、派生オブジェクトもから派生しA、サブオブジェクトがあることを意味しAます..そして、クラスが最初にから継承するAため、そのサブオブジェクトは「最初」である必要があります。DerivedA

コンパイラはそれをどのように承認しますか? 正しく動作させるために何を追加しますか?

また、オブジェクトを削除するときにメモリを正しく解放するにはどうすればよいですか?

4

6 に答える 6

7

簡単な答えは次のとおりです。魔法によって。

中程度の答えは次のとおりです。心配する必要はありません。標準はこれが機能すると述べており、それを機能させる方法を理解するのはコンパイラ次第です。

長い答え: これはコンパイラに依存するため、コンパイラのドキュメントを読んでください! 多くの C++ コンパイラは Itanium C++ ABI を実装しているので、それが出発点です。ポリモーフィック継承の一部として、各クラスには通常、いわゆるvtableがあり、関数ポインターの束が格納されますが、RTTI 情報と結合された仮想破壊およびメモリ割り当て解除ロジックも格納されます。考えてみてください。delete pb;正しいデストラクタを正しい順序で呼び出す必要があるだけでなく、解放関数に正しいポインタを渡す必要もあります。この情報はすべて、クラス階層のさまざまな vtable に含まれています。

于 2013-04-24T07:40:58.840 に答える
0

派生クラス宣言で最初に入力するクラスは問題ではありません。「A を最初にする必要があります」は正しくありません。それらは同じように基底クラスだからです。唯一の違いは、どのコンストラクター/デストラクタが最初に呼び出されるかです (基本クラスとして宣言された順序/逆の順序で)。

多重継承が悪い考えである理由を参照できます

おそらく、B クラスが A から派生し、C クラスが B から派生する単一継承が適していると思われます。

于 2013-04-24T07:43:04.610 に答える
0

フランスのサイトからのソース: C++ FAQ

Les constructeurs sont appelés dans l'ordre suivant :

le constructeur des classes de base héritées virtuellement en profondeur croissante et de gauche à droite ;
le constructeur des classes de base héritées non virtuellement en profondeur croissante et de gauche à droite ;
le constructeur des membres dans l'ordre de leur déclaration ;
le constructeur de la classe.

コンストラクターの呼び出しの順序:

base class constructors from virtual inheritance (BFS style and from left to rigth);
base class constructors from non virtual inheritance (BFS style and from left to rigth);
instances constructor in the order of their declaration;
Class constructor

デストラクタの呼び出し順序はこの順序の逆だと思います。

于 2013-04-24T07:43:41.727 に答える
0

一般的な答えは、それが機能するということですが、実際の実装はコンパイラに依存するため、詳細に依存するべきではありません (ただし、ポインターを扱うときに誤った仮定をしないように覚えておくとよいでしょう)。

多重継承が使用されている場合、 のような単純な行B* pb = new Derivedは、ポインターの実際の値を暗黙的に変更します。この特定のケースでは、コンパイラはDerivedポインタをに変換するB*必要があることを認識しているため、ポインタをどの程度変更する必要があるかを正確に認識しています (たとえばsizeof(A)、もちろん実際の値はおそらく異なります)。

仮想継承を使用している場合 (これは、共通の基本クラスが 1 回だけ含まれることを保証します。たとえば、両方ABから継承された場合CommonBase)、単純なポインター変換はさらに複雑になり、コンパイラーは vtable ルックアップを実行して実際のオフセットを見つけます。ポインター変換に使用する必要があります。

Visual Studio を使用している場合は、この行にブレークポイントを作成し、Alt+8 を押して逆アセンブリを表示すると、ポインター変換の背後にある「魔法」が明らかになります。

于 2013-04-24T07:58:16.947 に答える
0

ここではデストラクタが (正しく) 仮想であるため、コンパイラにはまったく問題はありません。適切なデストラクタ (~Derived()この場合) を呼び出すだけで、すべて正常に動作します。

標準では実装が強制されていませんが、多くのコンピューターで使用されている一般的なものは仮想テーブルです。少なくとも 1 つの仮想関数を持つクラスに属するすべてのオブジェクト (ここでのケースのように) は、それらのクラスに対応するvptr仮想テーブル ( ) へのポインター ( ) を持っています。vtblこれvtblには、オブジェクト クラスのすべての仮想関数の正しいオーバーライドへの参照があります。ここでは、静的クラス (ポインターのクラス) ではなく、動的クラス (つまり、オブジェクトの実際のクラス) について話しています。この場合:

  • インスタンスを指しているため、の動的タイプはpbです。DerivedDerived
  • として宣言されているため、の静的型はpbis です。BB*

ご覧のとおり、この場合、コンパイラは静的型を気にしません。命令を次のように解釈しますdelete pb(疑似コード)。

pb->vptr->destructor();
operator delete(pb); // Not exactly, but this is immaterial in this case

2 行目は無視してかまいません。ランタイムはこのチェーンをたどり、呼び出す適切なデストラクタを見つけます ( Derived())。順番に正しい順序でDerived()呼び出します。~A()~B()

于 2013-04-24T07:49:35.093 に答える