これはインタビューで尋ねられました。
独自のdynamic_castを作成する方法。typeidのname関数に基づいていると思います。
では、独自のtypidを実装する方法は?私にはそれについての手がかりがありません。
これはインタビューで尋ねられました。
独自のdynamic_castを作成する方法。typeidのname関数に基づいていると思います。
では、独自のtypidを実装する方法は?私にはそれについての手がかりがありません。
手がかりがなく、またはのようdynamic_cast
ではない理由があります。これらは実際にポインター演算を実行し、ある程度タイプセーフです。static_cast
const_cast
reinterpret_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
アクセス仕様 ( public
、protected
および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
継承 ?それは動作するはずです...しかしテストされていませんコンパイラの外でこれを実装しようとしている人には幸運を祈ります:x
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を実装するのには理由があります」に同意します。おそらくコンパイラは(特にサンプルコードに関して)より良いことをします。
わずかに定義されていない場合は、わずかにルーティンで回答を試みるには、次のようにします。
必要なことは、ポインターを int* にキャストし、スタックに新しい型 T を作成し、それへのポインターを int* にキャストし、両方の型の最初の int を比較することです。これにより、vtable アドレスの比較が行われます。それらが同じタイプの場合、同じ vtable を持ちます。そうでなければ、そうではありません。
私たちのより賢明な人は、クラスに整数定数を貼り付けます。
簡単。仮想関数WhoAmI()を使用して、typeidインターフェイスからすべてのオブジェクトを取得します。すべての派生クラスでオーバーライドします。