-1

次のプログラムを検討してください。

class Base
{
private:
    int m_nID;
public:
    Base()
    {
        m_nID = ClassID();
    }

    // ClassID returns a class-specific ID number
    virtual int ClassID() { return 1; }

    int GetID() { return m_nID; }
};

class Derived: public Base
{
public:
    Derived()
    {
    }

    virtual int ClassID() { return 2; }
};

int main()
{
    Derived cDerived;
    cout << cDerived.GetID(); 
    return 0;
}

上記の例では、派生IDは驚くべきことに2ではなく1です。ここで同じ質問に関する同様の質問をすでに見つけましたが、これが間違っている場合、どのように識別しますか? (派生)クラスメンバーとそれを使用しますか?つまり、各クラス(基本クラス、最初の派生クラス、基本クラスの派生である2番目の派生クラスまたは2番目の派生クラス)に一意のIDまたはタイプ名を割り当てたいと思いますクラスなど)、この点で私はどのように進んで行動することができますか?正しい方法は、クラスオブジェクトのインスタンス化時にコンストラクターでID /名前を割り当てて、型がすぐにわかるようにすることだと思います。上記のアプローチは失敗します。この点で他にどのようなオプションがありますか?

4

3 に答える 3

3

答えは簡単です。C++オブジェクトは、構築されるまで使用できません。基本クラスのサブオブジェクトをまだ作成中の場合は、まだ作成されていないため、派生クラスのオブジェクトを使用できません。(現在構築中のオブジェクトのvptrは、基本クラスコンストラクター内にいる限り、基本クラスvtableを指します。派生クラスコンストラクターに到達するまで更新されません。)

しかし、基本クラスのコンストラクターは、それが通常のオブジェクトに対して呼び出されているのか、サブオブジェクトに対して呼び出されているのかをどのように判断するのでしょうか。まあ、それが世界に関する他のランダムな情報を伝えるのと同じように、あなたはそれを明示的に伝えなければなりません。例えば:

struct Base {
    Base() { puts("I am a whole object!"); }
  protected:
    Base(bool is_subobject) { assert(is_subobject); puts("I'm only part of an object."); }
};

struct Derived : Base {
    Derived(): Base(/*is_subobject=*/true) { }
};

あなたがそれについて本当に賢いズボンになりたいのなら、あなたはテンプレートメタプログラミングを使うことができます:

struct Base {
    int m_nID;

    template<typename T>
    Base(T *)
    {
#ifdef UNSAFE
        // This breaks a lot of rules, but it will work in your case.
        // "this" is a Base, not a Derived, so the cast is undefined;
        // and if ClassID tried to use any non-static data members,
        // you'd be hosed anyway.
        m_nID = reinterpret_cast<T*>(this)->T::ClassID();
#else
        // This version is guaranteed to work in standard C++,
        // but you lose that nice virtual-ness that you worked
        // so hard for.
        m_nID = T::staticClassID();
#endif
    }

    virtual int ClassID() { return 1; }
    static int staticClassID() { return 1; }
    int GetID() { return m_nID; }
};

struct Derived : Base {
    Derived(): Base(this) { }
    virtual int ClassID() { return 2; }
    static int staticClassID() { return 2; }
};

int main() {
    Derived cDerived;
    std::cout << cDerived.GetID() << std::endl; // prints "2"
}

しかし、私がこの回答を作成しているときにDave Sが言ったように...あなたの例がすべてあなたがしているのであればint nID、パラメーターとして受け取る保護されたBaseコンストラクターを持ち、仮想メソッドを完全に忘れることができます。

于 2012-08-23T20:26:16.870 に答える
3

Karoly が言ったことを詳しく説明すると、コンストラクターでクラス ID を使用することを避けることができるかもしれませんが、構築後に次のことができます。

cout << cDerived.ClassID();

同じものを返す 2 つの関数を使用する理由はありません。またint m_nID;、すべてのオブジェクトに格納してメモリを浪費する理由もありません。

また、次のように基本クラスを変更する必要があります。

virtual int ClassID() = 0;

私は試していませんが、Base コンストラクターで ClassID を呼び出そうとすると、コンパイラ エラーが発生するはずです。また、Base が抽象クラスになるため、その新しいインスタンスを作成することはできません (これは良いことです)。

于 2012-08-23T20:20:33.633 に答える
2

オブジェクトには type のサブDerivedオブジェクトが含まれており、オブジェクトはBaseオブジェクトのBase「内部」に存在しDerivedます。文字通りその中に。オブジェクトのアドレスとそのBaseサブオブジェクトのアドレスを取得すると、オブジェクトBaseが占有するメモリ領域内のどこかにありDerivedます。

コンストラクターを構築するDerivedと、最初に各基本クラスが構築され、次に各メンバーが構築されます。したがって、Derived::Derived()実行が開始されたときに最初に起こることは、それBase::Base()が実行されることです。そのコンストラクターの間、オブジェクトの動的型はまだパーツDerivedを構築していないため、まだです。したがって、コンストラクターDerived中に仮想関数を呼び出すと、これまでに構築された唯一のオブジェクトであるパー​​ツの最終的なオーバーライドが検出されます。BaseBase

ボンネットの下で何が起こるかというと、Baseコンストラクターが開始すると、オブジェクトの vptr が の vtable を指すように設定されるため、の仮想関数Baseを指します。Baseそれが完了し、Derivedオブジェクト コンストラクターが実行された後、vptr をDerivedの vtable を指すように更新するため、オーバーライドする関数を参照しDerivedます。そのため、コンストラクターが終了するまでBase、オブジェクトの vptr は によって定義された仮想関数へのポインターのみを導きますBase

コンストラクターがvptrを更新するDerivedと、仮想関数を呼び出すとDerivedオーバーライドが呼び出されるため、質問に対する答えはm_nId、オーバーライドされた関数を呼び出して派生クラスIDを提供するときに、派生コンストラクターで再割り当てすることです。

于 2012-08-23T20:31:20.963 に答える