这是面试中被问到的问题。
如何编写自己的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 个字节因此,为了能够在一个派生对象和其基类之间进行强制类型转换,需要知道对象的内存布局。这只有编译器知道,该信息无法通过任何 API 访问,也没有标准化或其他什么东西。
在代码中,这将被翻译为:
Derived derived;
Base2* b2 = reinterpret_cast<Base2>(((char*)&derived) + 2);
static_cast
的。dynamic_cast
的实现中使用 static_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
遵守访问规范(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
Cell
。 - Matthieu M.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是有原因的”的说法;可能是因为编译器做得更好(特别是在示例代码方面!)。
dynamic_cast
? - CB Baileyreinterpret_cast
将ClassId
特化的结果作为DynamicCast
的结果。毫无疑问,这是错误的类别转换;即使它是正确的类别,reinterpret_cast
也不会对MI层次结构进行适当的调整。我是否在你的解决方案中漏掉了一些明显的东西? - CB Baileystatic_cast
也不正确。考虑 struct A { virtual ~A(); }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C {};
即使 B
和 C
可能是同一个 D
实例的子对象指针,它们之间的 static_cast
是无效的,而且 reinterpret_cast
极不可能产生正确的结果。只有 dynamic_cast
可以保证产生正确的结果,但这就是我们想要复制的。 - CB Bailey为了尝试一个略微不那么常规但又略微不那么明确的答案:
你需要做的是将指针转换为int*,在堆栈上创建一个新类型T,将其指针转换为int*,并比较两种类型中的第一个int。这将进行vtable地址比较。如果它们是相同类型,则它们将具有相同的vtable。否则,它们不会。
我们中更明智的人只需在我们的类中插入一个整数常量即可。
dynamic_cast
,以及如何执行转换。 - CB Bailey