5

これはインタビューで尋ねられました。

独自のdynamic_castを作成する方法。typeidのname関数に基づいていると思います。

では、独自のtypidを実装する方法は?私にはそれについての手がかりがありません。

4

4 に答える 4

20

手がかりがなく、またはのようdynamic_castではない理由があります。これらは実際にポインター演算を実行し、ある程度タイプセーフです。static_castconst_castreinterpret_cast

ポインター演算

これを説明するために、次の設計を考えてみましょう。

struct Base1 { virtual ~Base1(); char a; };
struct Base2 { virtual ~Base2(); char b; };

struct Derived: Base1, Base2 {};

のインスタンスは次のDerivedようになります (実際にはコンパイラに依存するため、gcc に基づいています...):

|      Cell 1       | Cell 2 |      Cell 3        | Cell 4 |
| vtable pointer    |    a   | vtable pointer     |    b   |
|         Base 1             |        Base 2               |
|                     Derived                              |

キャストに必要な作業を考えてみましょう。

  • DerivedからへのキャストにBase1は追加の作業は必要ありません。それらは同じ物理アドレスにあります
  • DerivedからへのキャストではBase2、ポインターを 2 バイトシフトする必要があります。

したがって、1 つの派生オブジェクトとそのベースの 1 つの間でキャストできるようにするには、オブジェクトのメモリ レイアウトを知る必要があります。そして、これはコンパイラーだけが認識しており、API を介して情報にアクセスすることはできず、標準化もされていません。

コードでは、これは次のように変換されます。

Derived derived;
Base2* b2 = reinterpret_cast<Base2>(((char*)&derived) + 2);

もちろん、これはstatic_cast.

さて、static_castの実装でを使用できた場合dynamic_castは、コンパイラを活用してポインタ演算を処理させることができますが、それでも問題はありません。

dynamic_cast を書いていますか?

まず最初に、 の仕様を明確にする必要がありdynamic_castます。

  • dynamic_cast<Derived*>(&base);baseのインスタンスでない場合は null を返しますDerived
  • dynamic_cast<Derived&>(base);std::bad_castこの場合は投げます。
  • dynamic_cast<void*>(base);最も派生したクラスのアドレスを返します
  • dynamic_castアクセス仕様 ( publicprotectedおよびprivate継承)を尊重する

あなたのことはわかりませんが、醜くなると思います。ここでは使用するtypeidだけでは不十分です。

struct Base { virtual ~Base(); };
struct Intermediate: Base {};
struct Derived: Base {};

void func()
{
  Derived derived;
  Base& base = derived;
  Intermediate& inter = dynamic_cast<Intermediate&>(base); // arg
}

ここでの問題はtypeid(base) == typeid(Derived) != typeid(Intermediate)、そのため、それに頼ることもできません。

もう一つの面白いこと:

struct Base { virtual ~Base(); };
struct Derived: virtual Base {};

void func(Base& base)
{
  Derived& derived = static_cast<Derived&>(base); // Fails
}

static_cast仮想継承が関係している場合は機能しません...そのため、ポインター算術計算の問題が忍び寄ってきました.

ほぼ解決

class Object
{
public:
  Object(): mMostDerived(0) {}
  virtual ~Object() {}

  void* GetMostDerived() const { return mMostDerived; }

  template <class T>
  T* dynamiccast()
  {
    Object const& me = *this;
    return const_cast<T*>(me.dynamiccast());
  }

  template <class T>
  T const* dynamiccast() const
  {
    char const* name = typeid(T).name();
    derived_t::const_iterator it = mDeriveds.find(name);
    if (it == mDeriveds.end()) { return 0; }
    else { return reinterpret_cast<T const*>(it->second); }
  }

protected:
  template <class T>
  void add(T* t)
  {
    void* address = t;
    mDerived[typeid(t).name()] = address;
    if (mMostDerived == 0 || mMostDerived > address) { mMostDerived= address; }
  }

private:
  typedef std::map < char const*, void* > derived_t;
  void* mMostDerived;
  derived_t mDeriveds;
};

// Purposely no doing anything to help swapping...

template <class T>
T* dynamiccast(Object* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T const* dynamiccast(Object const* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T& dynamiccast(Object& o)
{
  if (T* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

template <class T>
T const& dynamiccast(Object const& o)
{
  if (T const* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

コンストラクターにはいくつかの小さなものが必要です。

class Base: public Object
{
public:
  Base() { this->add(this); }
};

それでは、確認しましょう:

  • 古典的な使用法: わかりました
  • virtual継承 ?それは動作するはずです...しかしテストされていません
  • アクセス指定子を尊重して... ARG :/

コンパイラの外でこれを実装しようとしている人には幸運を祈ります:x

于 2010-07-04T11:40:36.387 に答える
1

1つの方法は、クラスIDを定義する静的識別子(たとえば整数)を宣言することです。クラスでは、クラス識別子を返す静的ルーチンとスコープルーチンの両方を実装できます(ルーチンを仮想としてマークするためのRemeber)。

静的識別子は、アプリケーションの初期化時に初期化されます。1つの方法は、クラスごとにInitializeIdルーチンを呼び出すことですが、これは、クラス名がわかっている必要があり、クラス階層が変更されるたびに初期化コードが変更されることを意味します。別の方法は、構築時に有効な識別子をチェックすることですが、クラスが構築されるたびにチェックが実行されるため、オーバーヘッドが発生しますが、最初だけが有用です(さらに、クラスが構築されていない場合、静的ルーチンは有用ではありませんl識別子は初期化されないため)。

公正な実装は、テンプレートクラスである可能性があります。

template <typename T>
class ClassId<T>
{
    public:

    static int GetClassId() { return (sClassId); }

    virtual int GetClassId() const { return (sClassId); }

    template<typename U> static void StateDerivation() {
        gClassMap[ClassId<T>::GetClassId()].push_back(ClassId<U>::GetClassId());
    }

    template<typename U> const U DynamicCast() const {
        std::map<int, std::list<int>>::const_iterator it = gClassMap.find(ClassId<T>); // Base class type, with relative derivations declared with StateDerivation()
        int id = ClassId<U>::GetClassId();

        if (id == ClassId<T>::GetClassId()) return (static_cast<U>(this));

        while (it != gClassMap.end()) {
            for (std::list<int>::const_iterator = pit->second.begin(), pite = it->second->end(); pit != pite; pit++) {
                if ((*pit) == id) return (static_cast<U>(this));
                // ... For each derived element, iterate over the stated derivations.
                // Easy to implement with a recursive function, better if using a std::stack to avoid recursion.
            }
        }

        return (null); 
    }  

    private:

    static int sClassId;
}

#define CLASS_IMP(klass) static int ClassId<klass>::sClassId = gClassId++;

// Global scope variables    

static int gClassId = 0;
static std::map<int, std::list<int>> gClassMap;

CLASS_IMPは、ClassIdから派生するクラスごとに定義され、gClassIdとgClassMapはグローバルスコープで表示されます。

使用可能なクラス識別子は、すべてのクラスがアクセスできる単一の静的整数変数(基本クラスClassIDまたはグローバル変数)によって保持され、新しいクラス識別子が割り当てられるたびに増分されます。

クラス階層を表すには、クラス識別子とその派生クラスの間のマップで十分です。特定のクラスにキャストできるクラスがあるかどうかを知るには、マップを反復処理して、派生の宣言を確認します。

直面する多くの困難があります...参照の使用!仮想派生!悪いキャスティング!クラスタイプマッピングの初期化が正しくないと、キャストエラーが発生します...


クラス間の関係は手動で定義し、初期化ルーチンでハードコーディングする必要があります。これにより、クラスが派生したものか、2つのクラスが共通の派生物であるかを判断できます。

class Base : ClassId<Base> {  }
#define CLASS_IMP(Base);
class Derived : public Base, public ClassId<Derived> {  }
#define CLASS_IMP(Derived);
class DerivedDerived : public Derived, public ClassId<DerivedDerived> {  }
#define CLASS_IMP(DerivedDerived);

static void DeclareDerivations()
{
    ClassId<Base>::StateDerivation<Derived>();
    ClassId<Derived>::StateDerivation<DerivedDerived>();
}

個人的には、「コンパイラがdynamic_castを実装するのには理由があります」に同意します。おそらくコンパイラは(特にサンプルコードに関して)より良いことをします。

于 2010-07-04T08:09:21.530 に答える
0

わずかに定義されていない場合は、わずかにルーティンで回答を試みるには、次のようにします。

必要なことは、ポインターを int* にキャストし、スタックに新しい型 T を作成し、それへのポインターを int* にキャストし、両方の型の最初の int を比較することです。これにより、vtable アドレスの比較が行われます。それらが同じタイプの場合、同じ vtable を持ちます。そうでなければ、そうではありません。

私たちのより賢明な人は、クラスに整数定数を貼り付けます。

于 2010-07-04T08:45:03.933 に答える
-1

簡単。仮想関数WhoAmI()を使用して、typeidインターフェイスからすべてのオブジェクトを取得します。すべての派生クラスでオーバーライドします。

于 2010-07-04T08:03:00.417 に答える