• C++多重继承


    今天在考虑一个C++实现的时候需要用到多重继承,突然发觉这么多年来基本都没怎么用过多重继承

    只是了解多重继承可以用,不过很容易出问题,尤其是基类是从相同的类派生出来的。

    今天我需要用到的没有这么麻烦,不过我还是对于多重继承的内存释放感到困惑。

    大家应该都知道设计C++类的时候,如果这个类需要被继承,建议把析构函数声明为virtual的。

    之前对于这一点的理解是:

    基类成员的内存释放一般是在基类析构函数里面完成的,如果基类析构函数不是虚函数,派生类析构的时候不会调用基类的,就会导致内存泄露。

    下面是随手写的一个多重继承测试用例

    class Interface1
    {
    public:
        
    ~Interface1()
        {
            printf(
    "~Interface1\n");
        };
        
    virtual void Func1() = 0;
    protected:
        
    int mData1;
    };

    class Interface2
    {
    public:
        
    ~Interface2()
        {
            printf(
    "~Interface2\n");
        };
        
    virtual void Func2() = 0;
    protected:
        
    int mData2;
    };

    class Base
    {
    public:
        
    ~Base()
        {
            printf(
    "~Base\n");
        };
        
    void        Func3()
        {
            printf(
    "Func3\n");
        }
    protected:
        
    int mData;
    };

    class Derived : public Base, public Interface1, public Interface2
    {
    public:
        
    ~Derived()
        {
            printf(
    "~Derived\n");
        };
        
    void Func1()
        {
            printf(
    "Func1\n");
        }

        
    void Func2()
        {
            printf(
    "Func2\n");
        }
    };

    ////////////////////////////////////////////
    // Test case
    ////////////////////////////////////////////
    Derived* d = new Derived();
    Base
    * b = d;
    Interface1
    * i1 = d;
    Interface2
    * i2 = d;
    i1
    ->Func1();
    i2
    ->Func2();
    b
    ->Func3();
    delete i2;

    问题来了,delete i2;这一行会导致Assert(Debug版)

    这是为什么呢?

    先让我们在watch window里面看看这些变量

    i1    0x004e99c0    Interface1 *
    i2    
    0x004e99c8    Interface2 *
    b    
    0x004e99d0    Base *
    d    
    0x004e99c0    Derived *

    Wow,地址竟然都不一样,想想也对,因为虚表的原因。具体的可以参考:这里

    当我把所有析构函数改成virtual的以后,delete就没有问题了。

    这中间发生了什么事情呢?因为i2的地址和我们分配的地址明显是不一样,直接free这块内存的话,当然会出错

    查看正确的析构代码的汇编代码发现窍门在这里

    i2的Interface2虚表中的析构函数指向了下面这个地址

    [thunk]:Derived::`vector deleting destructor':
    004117D0  sub         ecx,10h 
    004117D3  jmp         Interface2::`scalar deleting destructor
    ' (4113C5h) 

    ==>
    004113C5  jmp         Derived::`scalar deleting destructor' (411860h) 

    注意sub ecx, 10h,经过这个变幻以后得到的地址就是我们前面真正的内存地址。

    也就是说在每个基类的虚表中指向的析构函数其实并不是Derived的析构函数地址,而是一个编译器自动生成的中间代码地址,这段代码的功能就是把这个基类的实例内存地址(this指针)转换成内存分配时的真正地址(也就是当时实例化的类型),然后再调用实际类型的析构函数(Derived::~Derived())。

  • 相关阅读:
    URL中#号(井号)的作用
    Sublime Text2 快捷键汇总
    Sublime Text2 使用及插件配置
    Sumblime Text 2 常用插件以及安装方法
    CSS禁止选择文本功能(兼容IE,火狐等浏览器)
    JavaScript 判断 URL
    纯真IP数据库格式读取方法(JAVA/PHP/Python)
    Sublime Text 2 性感无比的代码编辑器!程序员必备神器!跨平台支持Win/Mac/Linux
    base64:URL背景图片与web页面性能优化
    数独DFS实现
  • 原文地址:https://www.cnblogs.com/hyamw/p/1871266.html
Copyright © 2020-2023  润新知