如何编写自己的dynamic_cast

时间:2010-07-04 07:56:36

标签: c++ dynamic-cast

在采访中已经提到了这一点。

如何编写自己的dynamic_cast。我认为,基于typeid的名称功能。

现在如何实现自己的typid?我对它一无所知。

4 个答案:

答案 0 :(得分:20)

有一个原因你没有任何线索,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                              |

现在想一下铸造所需的工作:

  • Derived投射到Base1不需要任何额外的工作,它们位于相同的实际地址
  • Derived转换为Base2必须将指针移位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的实例,则
  • Derived会返回null。
  • 在这种情况下,
  • 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

答案 1 :(得分:1)

ONe方式是声明一个定义类ID的静态标识符(例如一个整数)。在类中,您可以实现静态和作用域例程,它们返回类标识符( Remeber以标记例程虚拟)。

静态标识符应在应用程序初始化时初始化。一种方法是为每个类调用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是有原因的”;可能编译器会做得更好(特别是关于示例代码!)。

答案 2 :(得分:0)

如果稍微不那么明确,尝试稍微减少常规的答案:

您需要做的是将指针转换为int *,在堆栈上创建一个新类型T,将指针强制转换为int *,并比较两种类型中的第一个int。这将进行vtable地址比较。如果它们是相同的类型,它们将具有相同的vtable。否则,他们没有。

我们更明智的做法就是在课堂上坚持不变。

答案 3 :(得分:-1)

易。使用虚函数WhoAmI()从某个typeid接口派生所有对象。 在所有派生类中重写它。

相关问题