这在采访中被问到了。
如何编写自己的dynamic_cast。我认为,基于typeid的name函数。
现在如何实现自己的类型?我对此一无所知。
有一个原因你没有任何线索,dynamic_cast
并且static_cast
不像const_cast
or 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
toBase2
需要将指针移动 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
尊重访问规范(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
一种方法是声明一个定义类 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 是有原因的”;可能编译器做得更好(尤其是在示例代码方面!)。
如果稍微不那么定义,尝试稍微不那么常规的答案:
您需要做的是将指针转换为 int*,在堆栈上创建一个新类型 T,将指向它的指针转换为 int*,然后比较两种类型中的第一个 int。这将进行 vtable 地址比较。如果它们是相同的类型,它们将具有相同的 vtable。否则,他们不会。
我们中更明智的人只是在我们的课程中坚持一个完整的常数。
简单的。使用虚函数 WhoAmI() 从某个 typeid 接口派生所有对象。在所有派生类中覆盖它。