您的位置:首页 > 编程语言 > C语言/C++

C++ dynamic_cast实现原理

2014-08-12 11:48 211 查看
dynamic_cast是一个操作符,其用法不再赘述。查看汇编码可以发现实际调用的是这个函数__RTDynamicCast,其内部实现如下:

rtti.h:

#pragma once

extern "C" {
#include <windows.h>
};

typedef const type_info TypeDescriptor;

struct PMD
{
ptrdiff_t mdisp; //vftable offset
ptrdiff_t pdisp; //vftable offset
ptrdiff_t vdisp; //vftable offset(for virtual base class)
};

typedef const struct _s_RTTIBaseClassDescriptor
{
TypeDescriptor *pTypeDescriptor;
DWORD numContainedBases;
PMD where;
DWORD attributes;
} _RTTIBaseClassDescriptor;

typedef const struct _s_RTTIBaseClassArray
{
_RTTIBaseClassDescriptor* arrayOfBaseClassDescriptors[3];
}_RTTIBaseClassArray;

typedef const struct _s_RTTIClassHierarchyDescriptor
{
DWORD signature;
DWORD attributes;
DWORD numBaseClasses;
_RTTIBaseClassArray *pBaseClassArray;
}_RTTIClassHierarchyDescriptor;

typedef const struct _s_RTTICompleteObjectLocator
{
DWORD signature;
DWORD offset; //vftbl相对this的偏移
DWORD cdOffset; //constructor displacement
TypeDescriptor *pTypeDescriptor;
_RTTIClassHierarchyDescriptor *pClassDescriptor;
}_RTTICompleteObjectLocator;

#define BCD_NOTVISIBLE 0x00000001
#define BCD_AMBIGUOUS 0x00000002
#define BCD_PRIVORPROTINCOMPOBJ 0x00000004
#define BCD_PRIVORPROTBASE 0x00000008
#define BCD_VBOFCONTOBJ 0x00000010
#define BCD_NONPOLYMORPHIC 0x00000020

#define BCD_PTD(bcd) ((bcd).pTypeDescriptor)
#define BCD_NUMCONTBASES(bcd) ((bcd).numContainedBases)
#define BCD_WHERE(bcd) ((bcd).where)
#define BCD_ATTRIBUTES(bcd) ((bcd).attributes)

#define CHD_MULTINH 0x00000001 //多重继承
#define CHD_VIRTINH 0x00000002 //虚拟继承
#define CHD_AMBIGUOUS 0x00000004 //有重复基类的多重继承

#define CHD_SIGNATURE(chd) ((chd).signature)
#define CHD_ATTRIBUTES(chd) ((chd).attributes)
#define CHD_NUMBASES(chd) ((chd).numBaseClasses)
#define CHD_PBCA(chd) ((chd).pBaseClassArray)

#define COL_SIGNATURE(col) ((col).signature)
#define COL_OFFSET(col) ((col).offset)
#define COL_CDOFFSET(col) ((col).cdOffset)
#define COL_PTD(col) ((col).pTypeDescriptor)
#define COL_PCHD(col) ((col).pClassDescriptor)

extern "C" PVOID __cdecl __RTDynamicCast (PVOID, LONG, PVOID, PVOID, BOOL);

extern "C" PVOID __cdecl __RTtypeid (PVOID); // ptr to vfptr

#define TYPEIDS_EQ(pID1, pID2) ((pID1 == pID2) || !strcmp(pID1->name(), pID2->name()))



rtti.cpp:
#include <stdio.h>
#include <typeinfo>
#include "rtti.h"

#pragma warning(disable:4297)

static PVOID __cdecl FindCompleteObject(PVOID *);
static _RTTIBaseClassDescriptor * __cdecl FindSITargetTypeInstance(PVOID,_RTTICompleteObjectLocator *,TypeDescriptor *,int,TypeDescriptor *);
static _RTTIBaseClassDescriptor * __cdecl FindMITargetTypeInstance(PVOID,_RTTICompleteObjectLocator *,TypeDescriptor *,int,TypeDescriptor *);
static _RTTIBaseClassDescriptor * __cdecl FindVITargetTypeInstance(PVOID,_RTTICompleteObjectLocator *,TypeDescriptor *,int,TypeDescriptor *);
static ptrdiff_t __cdecl PMDtoOffset(PVOID pThis, const PMD& pmd);

extern "C" PVOID __cdecl __RTtypeid (PVOID inptr)
{
if (!inptr) {
throw std::bad_typeid ("Attempted a typeid of NULL pointer!");
return NULL;
}

__try {
// Ptr to CompleteObjectLocator should be stored at vfptr[-1]
_RTTICompleteObjectLocator *pCompleteLocator = (_RTTICompleteObjectLocator *) ((*((void***)inptr))[-1]);
return (PVOID) pCompleteLocator->pTypeDescriptor;
}
__except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH)
{
throw std::__non_rtti_object ("Access violation - no RTTI data!");
}
}

extern "C" PVOID __cdecl __RTDynamicCast (
PVOID inptr, // Pointer to polymorphic object
LONG VfDelta, // Offset of vfptr in object
PVOID SrcType, // Static type of object pointed to by inptr
PVOID TargetType, // Desired result of cast
BOOL isReference) // TRUE if input is reference, FALSE if input is ptr
{
PVOID pResult;
_RTTIBaseClassDescriptor *pBaseClass;

if (inptr == NULL)
return NULL;

__try {
PVOID pCompleteObject = FindCompleteObject((PVOID *)inptr);
_RTTICompleteObjectLocator *pCompleteLocator = (_RTTICompleteObjectLocator *) ((*((void***)inptr))[-1]);

// Adjust by vfptr displacement, if any
inptr = (PVOID *) ((char *)inptr - VfDelta);
// Calculate offset of source object in complete object
int inptr_delta = (char *)inptr - (char *)pCompleteObject;

if (!(CHD_ATTRIBUTES(*COL_PCHD(*pCompleteLocator)) & CHD_MULTINH)) { // if not multiple inheritance
pBaseClass = FindSITargetTypeInstance(pCompleteObject,
pCompleteLocator,
(TypeDescriptor *) SrcType,
inptr_delta,
(TypeDescriptor *) TargetType);
} else if (!(CHD_ATTRIBUTES(*COL_PCHD(*pCompleteLocator)) & CHD_VIRTINH)) { // if multiple, but not virtual, inheritance
pBaseClass = FindMITargetTypeInstance(pCompleteObject,
pCompleteLocator,
(TypeDescriptor *) SrcType,
inptr_delta,
(TypeDescriptor *) TargetType);
} else { // if virtual inheritance
pBaseClass = FindVITargetTypeInstance(pCompleteObject,
pCompleteLocator,
(TypeDescriptor *) SrcType,
inptr_delta,
(TypeDescriptor *) TargetType);
}

if (pBaseClass != NULL) {
// Calculate ptr to result base class from pBaseClass->where
pResult = ((char *) pCompleteObject) + PMDtoOffset(pCompleteObject, pBaseClass->where);
}else {
pResult = NULL;
if (isReference) {
throw std::bad_cast("Bad dynamic_cast!");
}
}

}
__except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER: EXCEPTION_CONTINUE_SEARCH) {
pResult = NULL;
throw std::__non_rtti_object ("Access violation - no RTTI data!");
}

return pResult;

}

/////////////////////////////////////////////////////////////////////////////
//
// FindCompleteObject - Calculate member offset from PMD & this
//
// Output: pointer to the complete object containing class *inptr
//
// Side-effects: NONE.
//
static PVOID __cdecl FindCompleteObject (PVOID *inptr) // Pointer to polymorphic object
{
// Ptr to CompleteObjectLocator should be stored at vfptr[-1]
_RTTICompleteObjectLocator *pCompleteLocator = (_RTTICompleteObjectLocator *) ((*((void***)inptr))[-1]);
char *pCompleteObject = (char *)inptr - pCompleteLocator->offset;
// Adjust by construction displacement, if any
if (pCompleteLocator->cdOffset)
pCompleteObject += *(ptrdiff_t *)((char *)inptr - pCompleteLocator->cdOffset);
return (PVOID) pCompleteObject;
}

static _RTTIBaseClassDescriptor * __cdecl FindSITargetTypeInstance (
PVOID pCompleteObject, // pointer to complete object
_RTTICompleteObjectLocator *pCOLocator, // pointer to Locator of complete object
TypeDescriptor *pSrcTypeID, // pointer to TypeDescriptor of source object
int SrcOffset, // offset of source object in complete object
TypeDescriptor *pTargetTypeID) // pointer to TypeDescriptor of result of cast
{
_RTTIBaseClassDescriptor *pBase;
_RTTIBaseClassDescriptor * const *pBasePtr;
DWORD i;

for (i = 0, pBasePtr = pCOLocator->pClassDescriptor->pBaseClassArray->arrayOfBaseClassDescriptors;
i < pCOLocator->pClassDescriptor->numBaseClasses;
i++, pBasePtr++) {

// Test type of selected base class
pBase = *pBasePtr;
if (TYPEIDS_EQ(pBase->pTypeDescriptor, pTargetTypeID) &&
!(BCD_ATTRIBUTES(*pBase) & BCD_NOTVISIBLE)) {
return pBase;
}
}
return NULL;
}

static _RTTIBaseClassDescriptor * __cdecl FindMITargetTypeInstance (
PVOID pCompleteObject, // pointer to complete object
_RTTICompleteObjectLocator *pCOLocator, // pointer to Locator of complete object
TypeDescriptor *pSrcTypeID, // pointer to TypeDescriptor of source object
int SrcOffset, // offset of source object in complete object
TypeDescriptor *pTargetTypeID) // pointer to TypeDescriptor of result of cast
{
_RTTIBaseClassDescriptor *pBase, *pSubBase;
_RTTIBaseClassDescriptor * const *pBasePtr, * const *pSubBasePtr;
DWORD i, j;

// First, try down-casts
for (i = 0, pBasePtr = pCOLocator->pClassDescriptor->pBaseClassArray->arrayOfBaseClassDescriptors;
i < pCOLocator->pClassDescriptor->numBaseClasses;
i++, pBasePtr++) {

pBase = *pBasePtr;
// Test type of selected base class
if (TYPEIDS_EQ(pBase->pTypeDescriptor, pTargetTypeID)) {
// If base class is proper type, see if it contains our instance of source class
for (j = 0, pSubBasePtr = pBasePtr+1;
j < pBase->numContainedBases;
j++, pSubBasePtr++) {

pSubBase = *pSubBasePtr;
if (TYPEIDS_EQ(pSubBase->pTypeDescriptor, pSrcTypeID) &&
(PMDtoOffset(pCompleteObject, pSubBase->where) == SrcOffset)) {
// Yes, this is the proper instance of source class
return pBase;
}
}
}
}

// Down-cast failed, try cross-cast
for (i = 0, pBasePtr = pCOLocator->pClassDescriptor->pBaseClassArray->arrayOfBaseClassDescriptors;
i < pCOLocator->pClassDescriptor->numBaseClasses;
i++, pBasePtr++) {

pBase = *pBasePtr;
// Check if base class has proper type, is accessible & is unambiguous
if (TYPEIDS_EQ(pBase->pTypeDescriptor, pTargetTypeID) &&
!(BCD_ATTRIBUTES(*pBase) & BCD_NOTVISIBLE) &&
!(BCD_ATTRIBUTES(*pBase) & BCD_AMBIGUOUS)) {
return pBase;
}
}

return NULL;
}

static _RTTIBaseClassDescriptor * __cdecl FindVITargetTypeInstance (
PVOID pCompleteObject, // pointer to complete object
_RTTICompleteObjectLocator *pCOLocator, // pointer to Locator of complete object
TypeDescriptor *pSrcTypeID, // pointer to TypeDescriptor of source object
int SrcOffset, // offset of source object in complete object
TypeDescriptor *pTargetTypeID) // pointer to TypeDescriptor of result of cast
{
_RTTIBaseClassDescriptor *pBase, *pSubBase;
_RTTIBaseClassDescriptor * const *pBasePtr, * const *pSubBasePtr;
_RTTIBaseClassDescriptor *pResult = NULL;
DWORD i, j;

// First, try down-casts
for (i = 0, pBasePtr = pCOLocator->pClassDescriptor->pBaseClassArray->arrayOfBaseClassDescriptors;
i < pCOLocator->pClassDescriptor->numBaseClasses;
i++, pBasePtr++) {

pBase = *pBasePtr;
// Test type of selected base class
if (TYPEIDS_EQ(pBase->pTypeDescriptor, pTargetTypeID)) {
// If base class is proper type, see if it contains our instance of source class
for (j = 0, pSubBasePtr = pBasePtr+1;
j < pBase->numContainedBases;
j++, pSubBasePtr++) {

pSubBase = *pSubBasePtr;
if (TYPEIDS_EQ(pSubBase->pTypeDescriptor, pSrcTypeID) &&
(PMDtoOffset(pCompleteObject, pSubBase->where) == SrcOffset)) {
// Yes, this is the proper instance of source class - make sure it is unambiguous
// Ambiguity now determined by inequality of offsets of source class within complete object, not pointer inequality
if ((pResult != NULL) && (PMDtoOffset(pCompleteObject, pResult->where) != PMDtoOffset(pCompleteObject, pBase->where))) {
// We already found an earlier instance, hence ambiguity
return NULL;
}
else {
// Unambiguous
pResult = pBase;
}
}
}
}
}

if (pResult != NULL)
return pResult;

// Down-cast failed, try cross-cast
for (i = 0, pBasePtr = pCOLocator->pClassDescriptor->pBaseClassArray->arrayOfBaseClassDescriptors;
i < pCOLocator->pClassDescriptor->numBaseClasses;
i++, pBasePtr++) {

pBase = *pBasePtr;
// Check if base class has proper type, is accessible & is unambiguous
if (TYPEIDS_EQ(pBase->pTypeDescriptor, pTargetTypeID) &&
!(BCD_ATTRIBUTES(*pBase) & BCD_NOTVISIBLE) &&
!(BCD_ATTRIBUTES(*pBase) & BCD_AMBIGUOUS)) {
return pBase;

}
}

return NULL;
}

static ptrdiff_t __cdecl PMDtoOffset(
PVOID pThis, // ptr to complete object
const PMD& pmd) // pointer-to-member-data structure
{
ptrdiff_t RetOff = 0;

if (pmd.pdisp >= 0) { // if base is in the virtual part of class
RetOff = pmd.pdisp;
RetOff += *(ptrdiff_t*)((char*)*(ptrdiff_t*)((char*)pThis + RetOff) + pmd.vdisp);
}

RetOff += pmd.mdisp;

return RetOff;
}


测试代码:
// WinDemo.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include "rtti.h"
using namespace std;

class A
{
public:
virtual void func()
{
cout << "A::func()" << endl;
}
};

class B : public A
{
public:
virtual void func()
{
cout << "B::func()" << endl;
}
};

class C : public A
{
public:
virtual void func()
{
cout << "C::func()" << endl;
}
private:
int _val;
};

int main(int argc, char* argv[])
{
A* pa = new C;
TypeDescriptor* ptypeA = &typeid(A);
TypeDescriptor* ptypeC = &typeid(C);
C* pc = (C*)__RTDynamicCast(pa, 0, (LPVOID)ptypeA, (LPVOID)ptypeC, FALSE);
cout << pc << endl;

return 0;
}
从以上代码可以看出:只能在有虚函数的类层次之间使用dynamic_cast。要实现dynamic_cast,编译器会在每个含有虚函数的类的虚函数表的前四个字节存放一个指向_RTTICompleteObjectLocator结构的指针,当然还要额外空间存放_RTTICompleteObjectLocator及其相关结构的数据。以上面代码的类C来说:



这个_RTTICompleteObjectLocator就是实现dynamic_cast的关键结构。里面存放了vfptr相对this指针的偏移,构造函数偏移(针对虚拟继承),type_info指针,以及类层次结构中其它类的相关信息。如果是多重继承,这些信息更加复杂。

所以,dynamic_cast的时间和空间代价是相对较高的,在设计时应避免使用。

如果整个工程都不需要dynamic_cast,可以禁用运行时类型信息(vs2008默认是启用的),这样编译器就不会产生_RTTICompleteObjectLocator及相关数据。

禁用方法如下:

依次选择【工程属性】、【配置属性】、【C/C++】、【语言】。将【启用运行时类型信息】改为”否“。


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  dynamic_cast 原理