zoukankan      html  css  js  c++  java
  • 多重继承下的类型转换

    主要解释强制类型转换的影响。因为static_cast会在编译期间检测,dynamice_cast会在运行时检测。
    强制类型检测在编译器没有足够的信息判断类型是否能够转换时只能像reinterpret_cast一样将地址赋值。
    #include <iostream>
    #include <hash_map>
    using namespace std;
    class I1
    {
    public:
    	virtual void vf1()
    	{
    		cout << "I'm I1:vf1()" << endl;
    	}
    };
    class I2
    {
    public:
    	virtual void vf2()
    	{
    		cout << "I'm I2:vf2()" << endl;
    	}
    };
    
    class C : public I1, public I2
    {
    private:
    	hash_map<string, string> m_cache;
    };
    
    I1* CreateC()
    {
    	return new C();
    }
    int main(int argc, char** argv)
    {
    	I1* pI1 = CreateC();
    	pI1->vf1();
    	I2* pI2 = (I2*)pI1;
    	pI2->vf2();
    	delete pI1;
    	return 0;
    }
    上述代码执行结果如下:

    先来看看前面代码的内存布局。

    20140425110212046

    之所以会出现pI1和pI2指向了同一个地址,是因为C++编译器没有足够的知识来把IA*类型转换为IB*类型,只能按照传统的C指针强制转换处理,也就是指针位置不变。为了验证上面的结论,简单的把pIA和pIB打印出来即可。把main()函数修改为如下:

    int main(int argc, char** argv) 

    %2Fa>唇v

        I1* pI1 = CreateC(); 

        pI1->vf1(); 

        I2* pI2 = (I2*)pI1; 

        pI2->vf2(); 

        cout << "pI1指向的地址为:"<<std::hex << pI1 << endl; 

        cout << "pI2指向的地址为:"<<std::hex << pI2 << endl;   

        delete pI1; 

        return 0; 

    执行结果为:

    20140425110720890

    可见pI1和pI2确实指向了同一个地址,而这个地址就是I1类的虚表。由于虚函数是按照顺序定位的,编译器编译pI2->vf2()的时候,不管实际的pI2指向哪里,都把它当做指向了I2的虚表,根据I2类定义,推出I2::vf2()这个函数位于其虚表的第0个位置,所以就直接把pI2指向的地址作为vf2来调用。而实际上,这个位置恰恰是I1虚表的第0个位置,也就是I1::vf1的位置,所以实际执行时调用的是I1::vf1()。其实这种情况是有些特殊的,也就是这个位置正好也是一个函数地址,而且函数原型也一样,要是有任何不同的地方,就会造成调用失败,反而更容易及时的提醒开发者。

    强制类型转换

    static_cast:进行编译期类型转换,此时如果C++编译期不能推算出指针调整算法,就会报错,提醒开发者。

    dynamic_cast:使用dynamic_cast进行运行期动态类型转换,这需要开启编译器的RTTI。

    reinterpret_cast:地址的转换,不需要检测类型。

    在C++中,指针的类型转换是经常发生的事情,比如将派生类指针转换为基类指针,将基类指针转换为派生类指针。指针的本质其实就是一个整数,用以记录进程虚拟内存空间中的地址编号,而指针的类型决定了编译器对其指向的内存空间的解释方式。C++中对指针进行类型转换,不会改变指针的值,只会改变指针的类型(即改变编译器对该指针指向内存的解释方式),但是这个结论在C++多重继承下是 不成立的

    #include <iostream>
    using namespace std;
    class CBaseA
    {
    public:
    char m_A[32];
    };
    class CBaseB
    {
    public:
    char m_B[64];
    };
    class CDerive : public CBaseA, public CBaseB
    {
    public:
    char m_D[128];
    };
    int main()
    {
    auto pD = new CDerive;
    auto pA = (CBaseA *)pD;
    auto pB = (CBaseB *)pD;
    cout << pA << ' ' << pB << ' ' << pD << endl;
    cout << (pD == pB) << endl;
    }

    这段代码的输出是:

    0x9f1080
    0x9f10a0
    0x9f1080
    1

    20140716154534734

    pB与pD的指针差值正好是CBaseA占用的内存大小32字节,而pA与pD都指向了同一段地址。这是因为,将一个派生类的指针转换成某一个基类指针,编译器会将指针的值偏移到该基类在对象内存中的起始位置。

    输出1表示pD和pB是相等的,而刚刚我们才说明了,pD和pB的地址是相差了32个字节的。

    其实这也是编译器为大家屏蔽了这种指针的差异,当编译器发现一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针做隐式类型提升已屏蔽多重继承带来的指针差异。因为两个指针做比较,目的通常是判断两个指针是否指向了同一个内存对象实例,在上面的场景中,pD和pB虽然指针值不等,但是他们确确实实都指向了同一个内存对象(即new CDerive;产生的内存对象 ),所以编译器又在此处插了一脚,让我们可以安享==运算的上层语义。

    参考地址:http://blog.csdn.net/smstong/article/details/24455371

  • 相关阅读:
    pycharm使用
    python上手之环境搭建
    LDA浅析转
    矩阵按列按行归一化到L2范数的原理和最精简Matlab代码(转)
    (转)Low-Mid-High
    菲波纳数列的特性
    劝狼赋
    asp.net mvc Controller 模式下的 aop
    android for vs (三)visual studio android 发布为 apk
    android for vs (二)visual studio android 开发实例
  • 原文地址:https://www.cnblogs.com/cthu/p/5179309.html
Copyright © 2011-2022 走看看