• 多重继承下的类型转换


    主要解释强制类型转换的影响。因为static_cast会在编译期间检测,dynamice_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) 

        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

  • 相关阅读:
    在 idea 下搭建的第一个MyBatis项目及增删改查用法
    解决idea中Tomcat服务器日志乱码及控制台输出乱码
    idea 设置自动生成方法的快捷键 类似于main() 方法
    windows下Tomcat根据日期生成日志catalina.out
    springmvc <from:from>标签的使用
    Spring+Quartz集群环境搭建
    Quartz整合Spring
    Quartz 基本编码
    java中 线程池和 callable 创建线程的使用
    SpringSecurity权限管理框架--基于springBoot实现授权功能
  • 原文地址:https://www.cnblogs.com/cthu/p/5179287.html
Copyright © 2020-2023  润新知