原创

C++ dynamic_cast实现原理

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/passion_wu128/article/details/38511957

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++】、【语言】。将【启用运行时类型信息】改为”否“。


                                    
文章最后发布于: 2014-08-12 11:48:51
展开阅读全文
0 个人打赏

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 1024 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览