C++ - 构造和析构期间vtable指针会被改变吗?

3

在调用成员函数时,不论是使用 this 指针还是其他方式,在派生类中并且在基类的构造函数 / 析构函数中,都会调用相关类的函数(包括虚拟函数)。

为什么会这样呢?在此过程中,对象的 vtable 指针是否被改变了?因为我理解一般情况下一个对象只有一个 vtable 指针,除非使用了多重继承。


以下代码可以说明我的意思:

#include <stdio.h>


class B {
    public:
    B()
        { printf("B constructor!\n"); f(); g(); }
    virtual ~B()
        { printf("B destructor!\n");  f(); g(); }
    virtual void f() 
        { printf("f() in B!\n"); }
    void g() 
        { printf("g() in B!\n"); }
    void h() 
        { printf("h() in B!\n"); }
};

class D : public B {
    public:
    D()
        { printf("D constructor!\n"); f(); g(); }
    virtual ~D()
        { printf("D destructor!\n");  f(); g(); }
    virtual void f() 
        { printf("f() in D!\n"); }
    void g() 
        { printf("g() in D!\n"); h(); }
};

int main()
{
    D *d = new D;
    B *b = d;
    delete b;
}

在构造函数和析构函数中,调用所创建/销毁对象的成员函数。

2
简而言之:是的(尽管这是一个实现细节)。你观察到的是语言设计的运作方式。 - Paul Sanders
3
常见的实现技术是在构造函数中首先设置对象的虚表指针。因此,B 的构造函数将其设置为指向 B 的虚表,然后 D 的构造函数修改它以指向 D 的虚表。再次强调,这只是实现细节——重要的是标准规定正在运行构造函数/析构函数的类是正在构建或销毁的对象的动态类型。 - Igor Tandetnik
1
是的,如果在派生类中引入新的虚函数,它就会被改变。 - 273K
1个回答

13

一个对象的类型就是它的类型,直到它不是。

根据C++的规定,对象的构造函数必须按照特定的顺序调用。因为派生类实例在任何时候都是基类实例,所以在调用派生类实例之前需要调用基类实例的构造函数。

但是,如果这是情况,那么在调用基类构造函数时对象是什么?派生类构造函数甚至还没有开始执行,因此把它视为派生类实例还没有意义。

所以它不是派生类实例。

因此,在派生类初始化过程中基类构造函数的执行期间,所有虚函数调用作为该类的全部内容:一个基类实例。它还不是派生类类型的实例,因此您还不能调用任何派生类成员。

我的意思是,可以使用static_cast将基类指针转换为派生类实例,但是使用这样的指针会产生未定义行为,因为您在访问尚未初始化的派生类类型对象。

析构函数中发生相反的情况。先调用派生类析构函数,然后是基类。但是,在派生类析构函数完成后......对象不再是派生类实例。因此,在基类析构函数中所有虚函数调用都是基类方法。

在基于虚函数表(vtable)的实现中,该行为是通过在初始化过程中在构造函数/析构函数之间的不同点更改vtable指针来实现的。基类构造函数将vtable设置为指向基类vtable。当派生类析构函数启动时,它将vtable设置为指向派生类vtable。

这是一个关于该主题的很好的阐述,谢谢! - Anton Tretyakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接