5

这在采访中被问到了。

如何编写自己的dynamic_cast。我认为,基于typeid的name函数。

现在如何实现自己的类型?我对此一无所知。

4

4 回答 4

20

有一个原因你没有任何线索,dynamic_cast并且static_cast不像const_castor 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不需要任何额外的工作,它们位于相同的物理地址
  • 转换 from DerivedtoBase2需要将指针移动 2 个字节

因此,有必要知道对象的内存布局,以便能够在一个派生对象和它的基础对象之一之间进行转换。这只有编译器知道,信息不能通过任何 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

一种方法是声明一个定义类 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;

应为从 ClassId 派生的每个类定义 CLASS_IMP,并且 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 是有原因的”;可能编译器做得更好(尤其是在示例代码方面!)。

于 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 回答