设为首页 加入收藏

TOP

C++对象模型:g++的实现(五)(一)
2023-07-23 13:33:45 】 浏览:86
Tags:

这篇博客来讲一下g++实现的C++对象模型中的虚函数的实现,包括:单一继承体系下的虚函数,多继承下的虚函数和虚继承下的虚函数。其中虚继承下的虚函数在《深度探索C++对象模型》中只是说很复杂,受限于技术力和查到的资料,这里我只是对于g++的部分实现进行观察。

1. 单一继承体系下的虚函数

在前面的博客中我们已经通过对虚表的探索讲了虚函数的一般实现,大体上来说就是编译器会在适当的时候(在单一继承体系中就是当类中第一次出现虚函数的时候)添加一个虚表指针,指向属于该类的虚函数表,而所有虚函数的地址会出现在虚表指针的固定表项,也就是说在继承体系下的一个虚函数会被赋予固定的虚表下标。当派生类覆写(override)了基类的虚函数时,新的虚函数的地址会出现在基类虚函数在虚表中的位置,在多态调用虚函数时从虚表中取出虚函数地址来调用,从而实现多态。
一般而言,在单一继承体系下每一个类都只有一个虚表,在这个虚表中存有所有active virtual functions(中文版《深度探索C++对象模型》没有翻译,我这里也直接使用了,在我的理解里就是派生类所有有效的、能用的虚函数)的地址。这些active virtual functions包括:

  • 该类所定义的所有虚函数,包括其覆写(override)的基类的虚函数;
  • 继承自基类的虚函数,如果派生类不覆写这些虚函数的话;
  • 一个pure_vairtual_called()函数实体,她既可以扮演pure virtual function的空间保卫者角色,也可以当作异常处理函数(有时候会用到)【《深度探索C++对象模型》原话】
// test23.cpp

class Base {
public:
    Base(int i)
        : m_i(i)
    {}

    virtual
    ~Base() {
        m_i = 0;
    }

    virtual
    int getInt() {
        return m_i;
    }

    virtual
    void increaseInt() {
        m_i++;
    }

    virtual
    long getLong() = 0;
private:
    int m_i;
};

class Derived: public Base {
public:
    Derived(int i, long l)
        : Base(i),
          m_l(l)
    {}

    virtual
    ~Derived() {
        m_l = 0;
    }

    virtual
    int getInt() override {     // overrid Base::getInt()
        return Base::getInt() + 1;
    }

    virtual
    long getLong() override {   // overrid Base::getLong(),在Base中是一个纯虚函数
        return m_l;
    }

    virtual
    void increaseLong() {       // new virtual function
        ++m_l;
    }
private:
    long m_l;
};

int main() {
    Derived* pd = new Derived(1, 2L);
    int i = pd->getInt();
    pd->increaseInt();
    long l = pd->getLong();
    pd->increaseLong();
    pd->~Derived();
    delete pd;
}

gdb调试test23.cpp编译出的程序
另外,在这里我们可以注意到一个问题,虚表指针指向的空间,前两个表项都显示是Derived::~Derived(),也就是都是析构函数,而且地址不一样,这是怎么回事?我们看一下这两处地方的汇编代码:
Derived::~Derived()反汇编
可以看到,第一个析构函数就是普通的析构函数它先调用了我们自己定义的析构函数,再调用了基类的析构函数Base::~Base;而第二个虚构函数则是先调用了第一个析构函数,再调用了::operator delete(_ZdlPvm使用c++filt工具查看可知其就是operator delete(void*, unsigned long))。
那是不是就是当我们自己调用Derived::~Derived时调用第一个,使用delete操作符时调用的就是第二个呢?我们看到反汇编:
两个调用析构函数的方法的反汇编
可以看到确实是这样的。同时,我们还有一个小发现,就是当delete操作符操作的指针是nullptr时,是不会调用析构函数的,编译器真是相当费心了(在我的测试下好像是只有delete一个指向有虚析构函数的对象的指针时才会检查,否则就直接不检查调用::operator delete)。
关于最后一个,因为我们无法实例化抽象基类,所以使用-fdump-class-hierarchy选项查看类信息:

Vtable for Base
Base::_ZTV4Base: 7 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI4Base)
16    0
24    0
32    (int (*)(...))Base::getInt
40    (int (*)(...))Base::increaseInt
48    (int (*)(...))__cxa_pure_virtual

Class Base
   size=16 align=8
   base size=12 base align=8
Base (0x0x7f24b28e7960) 0
    vptr=((& Base::_ZTV4Base) + 16)

Vtable for Derived
Derived::_ZTV7Derived: 8 entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI7Derived)
16    (int (*)(...))Derived::~Derived
24    (int (*)(...))Derived::~Derived
32    (int (*)(...))Derived::getInt
40    (int (*)(...))Base::increaseInt
48    (int (*)(...))Derived::getLong
56    (int (*)(...))Derived::increaseLong

Class Derived
   size=24 align=8
   base size=24 base align=8
Derived (0x0x7f24b277d1a0) 0
    vptr=((& Derived::_ZTV7Derived) + 16)
  Base (0x0x7f24b28e7de0) 0
      primary-for Derived (0x0x7f24b277d1a0)

我们可以看到在Base类的48偏移处确实有一个__cxa_pure_virtual表项,应该就是所谓的pure_vairtual_called,在结合Derived类的虚表,在对应位置是Derived::getLong,说明正是使用该函数占位了Base::getLong这个虚函数。

2. 多重继承下的虚函数

在单一继承体系下一切都显得那么美好,完全不涉及到指针的调整,因为所有的指针转化都不需要做底层的调整,始终指向类的开头。你可能现在还不能理解,在看完这一部分后再来看上面这一句话就会感慨:啊,单一继承是这么简单的事!
但在多重继承下事情开始变得复杂,看下面的例子:

// test24.cpp

class Base1 {
pu
首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇std::weak_ptr<void>绑定到.. 下一篇C++对象模型:g++的实现(六)

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目