2010-07-04 151 views
5

這在採訪中已經提到。如何編寫自己的dynamic_cast

如何編寫自己的dynamic_cast。我認爲,基於typeid的名字功能。

現在如何實現自己的typid?我對此毫無頭緒。

+0

@ saurabh01返回通過你提出的問題,選擇最有用的答案(如果有的話)。點擊該答案左邊的複選框即可接受。謝謝! – 2010-07-04 08:31:03

+13

絕對是這個月最尷尬的面試問題的有力競爭者。 – 2010-07-04 08:38:00

+2

@尼爾巴特沃斯:我同意。編譯器實現DC是有原因的。 – Puppy 2010-07-04 08:45:32

回答

18

有你沒有任何線索,dynamic_caststatic_cast不像const_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        | 

現在想一下爲c asting:

  • 鑄造從DerivedBase1不需要任何額外的工作,他們是在同一個物理地址
  • 鑄造從DerivedBase2必要指針由2個字節

因此轉移,有必要知道能夠在一個派生對象和它的一個基礎之間進行轉換的對象的內存佈局。這只是編譯器所知道的,信息不能通過任何API訪問,也不是標準化的或者其他的。

在代碼中,這將轉化這樣的:

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

而這,當然,對於一個static_cast

現在,如果您在的實現中能夠使用static_cast,那麼您可以利用編譯器並讓它爲您處理指針算術......但您仍然沒有擺脫困境。

Writing dynamic_cast?第一

第一件事,我們需要澄清的dynamic_cast規格:

  • dynamic_cast<Derived*>(&base);,則返回null base不是Derived一個實例。
  • dynamic_cast<Derived&>(base);在這種情況下拋出std::bad_cast
  • dynamic_cast<void*>(base);返回最派生類
  • dynamic_cast尊重訪問規格(publicprotectedprivate繼承)

我不知道你的地址,但我認爲這將是醜陋。使用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

+0

在頂層的類佈局的圖形描述中,請將「Byte」更改爲其他內容,因爲vtable-pointer [幾乎肯定]不是Byte。 – iAdjunct 2016-10-13 14:50:50

+1

@iAdjunct:的確,我用'Cell'代替。 – 2016-10-13 15:02:07

-1

簡單。使用虛函數WhoAmI()從某個類型化接口派生所有對象。 在所有派生類中重寫它。

+0

怎麼樣上傳? – 2010-07-04 08:07:45

1

ONe的方式是聲明一個靜態標識符(例如整數),它定義了類ID。在該類中,您可以實現靜態和範圍例程,它將返回類標識符(記住標記例程虛擬)。

靜態標識符應在應用程序初始化時初始化。一種方法是爲每個類調用InitializeId例程,但這意味着必須知道類名,並且每次修改類層次結構時都應修改初始化代碼。另一種方法是在構建時檢查有效標識符,但是這會引入開銷,因爲每次構建類時都會執行檢查,但只有第一次是有用的(此外,如果沒有構建類,則不能使用靜態例程因爲標識符從未初始化)。

一個公平的實現可能是一個模板類:

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或全局變量)可訪問的單個靜態整數變量保存,每次分配新的類標識符時該變量都會增加。

爲了表示類層次結構,在類標識符及其派生類之間的映射就足夠了。要知道是否可以將某個類轉換爲特定的類,請遍歷該地圖並檢查聲明派生。

面對使用參考文獻有很多困難!虛擬派生!不好的鑄造!錯誤的類型映射初始化將導致鑄造錯誤...


類之間的關係應手動定義,用初始化例程硬編碼。這允許確定一個類是否派生自,或者是否兩個類作爲一個通用派生。

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的理由」;編譯器可能會更好地執行這些操作(特別是在示例代碼方面!)。

+0

假設你已經發現你有相同層次結構中的類的引用,你如何提出實際執行'dynamic_cast'? – 2010-07-04 08:57:05

+0

已更新。我認爲這顯然是解決方案的道路。 – Luca 2010-07-04 20:30:13

+0

作爲'DynamicCast'的結果,你似乎使用'ClassId'專用化的'reinterpret_cast'。當然,這是錯誤的課程,即使它是正確的課程,reinterpret_cast也不會對MI層次做適當的調整。我錯過了解決方案中顯而易見的事情嗎? – 2010-07-05 07:27:33

0

要試圖回答略少常規,如果略少定義:

你需要做的是轉換指針爲int *什麼,在棧上創建一個新的類型T,將指針給它int *,並比較兩種類型中的第一個int。這將做一個vtable地址比較。如果它們是相同的類型,它們將具有相同的vtable。否則,他們不。

我們更明智的只是在課堂上堅持積分常數。

+0

但是這並不能告訴你是否有可能在兩種類型之間使用'dynamic_cast'或者如何執行轉換。 – 2010-07-04 09:09:06