如何编写自己的dynamic_cast

发布于 2024-09-08 03:11:40 字数 102 浏览 6 评论 0原文

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

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

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

This have been asked in the interview.

How to write own dynamic_cast. I think, on the basis of typeid's name function.

Now how to implement own typid? I have no clue on it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不喜欢何必死缠烂打 2024-09-15 03:11:40

您没有任何线索是有原因的,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 不需要任何额外的工作,它们位于相同的物理地址
  • 转换>DerivedBase2 需要将指针移动 2 个字节

因此,有必要了解对象的内存布局,以便能够在一个派生对象与其基对象之一之间进行转换。这只有编译器知道,这些信息无法通过任何 API 访问,它不是标准化的或其他任何东西。

在代码中,这将翻译为:

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

当然,这是针对 static_cast 的。

现在,如果您能够在 dynamic_cast 的实现中使用 static_cast,那么您可以利用编译器并让它为您处理指针算术......但是您'我们仍然没有走出困境。

写dynamic_cast?

首先,我们需要明确dynamic_cast的规范:

  • dynamic_cast(&base);返回如果 base 不是 Derived 的实例,则为 null。
  • 在这种情况下,dynamic_cast(base); 会抛出std::bad_cast
  • dynamic_cast(base); 返回最派生类的地址
  • dynamic_cast 遵守访问规范 (public, protectedprivate 继承)

我不了解你,但我认为这会很丑陋。这里使用 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); }
};

那么,让我们检查一下:

  • 经典用途:可以
  • 虚拟继承吗?它应该可以工作...但没有
  • 根据访问说明符进行测试... ARG:/

祝任何试图在编译器之外实现此功能的人好运,真的:x

There is a reason you don't have any clue, dynamic_cast and static_cast are not like const_cast or reinterpret_cast, they actually perform pointer arithmetic and are somewhat typesafe.

The pointer arithmetic

In order to illustrate this, think of the following design:

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

struct Derived: Base1, Base2 {};

An instance of Derived should look something like this (it's based on gcc since it is actually compiler dependent...):

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

Now think of the work necessary for casting:

  • casting from Derived to Base1 does not require any extra work, they are at the same physical address
  • casting from Derived to Base2 necessitates to shift the pointer by 2 bytes

Therefore, it is necessary to know the memory layout of the objects to be able to cast between one derived object and one of its base. And this is only known to the compiler, the information is not accessible through any API, it's not standardised or anything else.

In code, this would translate like:

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

And that is, of course, for a static_cast.

Now, if you were able to use static_cast in the implementation of dynamic_cast, then you could leverage the compiler and let it handle the pointer arithmetic for you... but you're still not out of the wood.

Writing dynamic_cast ?

First things first, we need to clarify the specifications of dynamic_cast:

  • dynamic_cast<Derived*>(&base); returns null if base is not an instance of Derived.
  • dynamic_cast<Derived&>(base); throws std::bad_cast in this case.
  • dynamic_cast<void*>(base); returns the address of the most derived class
  • dynamic_cast respect the access specifications (public, protected and private inheritance)

I don't know about you, but I think it's going to be ugly. Using typeid is not sufficient here:

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

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

The issue here is that typeid(base) == typeid(Derived) != typeid(Intermediate), so you can't rely on that either.

Another amusing thing:

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

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

static_cast doesn't work when virtual inheritance is involved... so we've go a problem of pointer arithmetic computation creeping in.

An almost solution

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(); }
}

You need some little things in the constructor:

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

So, let's check:

  • classic uses: okay
  • virtual inheritance ? it should work... but not tested
  • respecting access specifiers... ARG :/

Good luck to anyone trying to implement this outside of the compiler, really :x

寻梦旅人 2024-09-15 03:11:40

一种方法是声明一个定义类 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是有原因的”;编译器可能会做得更好(特别是在示例代码方面!)。

ONe way is to declare a static identifier (an integer for example) which defines the class ID. In the class you could implement both static and scoped routines wich returns the class identifier (Remeber to mark routines virtual).

The static identifier shall be initialized at application initialization. One way is to call an InitializeId routine for each class, but this mean that the class names must be known, and the initialization code shall be modified each time the class hierarchy is modified. Another way is to check for valid identifier at construction time, but this introduces an overhead since each time a class is constructed the check is executed, but only the first time is usefull (additionally if no class is constructed, the static routine cannot be usefull since the identifier is never initialized).

A fair implementation could be a template class:

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 shall be defined in for each class deriving from ClassId, and gClassId and gClassMap shall be visible at global scope.

The available class identifiers are keeped by a single static integer variable accessible by all classes (a base class ClassID or a global variable), which is incremented each time a new class identifier is assigned.

To represents the class hierarchy is sufficient a map between the class identifier and its derived classes. To know whether any class can be casted to a specific class, iterate over the map and check declare derivations.

There are many difficulties to face... use of references! virtual derivations! bad casting! Bad class type mapping initialization will lead to casting errors...


Relationships between classes shall be defined manually, hardcoded with the initialization routine. This allow to determine whether a class derived from, or whether two classes as a common derivation.

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>();
}

Personally I agree with "there is a reason if compilers implements dynamic_cast"; probably the compiler do the things better (especially with respect of the example code!).

春花秋月 2024-09-15 03:11:40

要尝试一个稍微少一点的例程,如果定义稍微少一些:

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

我们中更明智的人只是在我们的班级中坚持一个不可或缺的常数。

To attempt an answer slightly less routine, if slightly less defined:

What you need to do is cast the pointer to an int*, create a new type T on the stack, cast a pointer to it to int*, and compare the first int in both types. This will do a vtable address comparison. If they are the same type, they will have the same vtable. Else, they don't.

The more sensible of us just stick an integral constant in our classes.

木槿暧夏七纪年 2024-09-15 03:11:40

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

Easy. Derive all objects from some typeid interface with a virtual function WhoAmI().
Override it in all deriving classes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文