【问题标题】:Why is a vptr required when the derived class doesn't override the virtual function?为什么派生类不覆盖虚函数时需要 vptr?
【发布时间】:2012-02-24 22:48:04
【问题描述】:
class base {
public:
    void virtual fn(int i) {
        cout << "base" << endl;
    }
};

class der : public base{
    public:
    void  fn(char i) {
        cout << "der" << endl;
    }
};

int main() {

    base* p = new der;
    char i = 5;
    p->fn(i);
    cout << sizeof(base);
    return 0;
}

这里base类中定义的函数fn的签名与der类中定义的函数fn()的签名不同,尽管函数名称相同。 因此,der 类中定义的函数隐藏了base 类函数fn()。所以类der版本的fn不能被p-&gt;fn(i)调用;没事。

我的意思是,如果没有使用 VTABLE 指针,为什么 sizeofbaseder4VTABLE 指针这里有什么要求?

【问题讨论】:

  • 你听说过 C++ 中的重载吗?
  • @KamilKlimek:重载是声明具有不同签名的多个函数的行为。您的意思可能是覆盖(即在派生类中重新实现方法)。
  • 供您参考,这些不是重载函数。
  • 这段代码实际上有隐藏问题:der::fn(char) 隐藏base::fn(int)
  • @KamilKlimek:重载不是跨类,编译器正确解释,这在标准中有很好的定义。

标签: c++ overriding virtual-functions vptr


【解决方案1】:

请注意,这高度依赖于实现,并且可能因每个编译器而异。

vtable 存在的要求是基类用于继承和扩展,从它派生的类可能会覆盖该方法。

Base 和 Derived 这两个类可能驻留在不同的翻译单元中,编译器在编译 Base 类时不会真正知道该方法是否会被覆盖。因此,如果它找到关键字virtual,它会生成vtable

【讨论】:

  • 但是在这个例子中,所有代码都在在同一个模块中,编译器可以知道。
【解决方案2】:

vtable通常不仅仅用于虚函数,当你做一些dynamic_cast或者程序访问类的type_info时,它也用来识别类的类型。

如果编译器检测到没有虚拟函数被覆盖并且没有使用任何其他功能,它只是可以删除 vtable 指针作为优化。

显然编译器编写者认为这样做不值得。可能是因为它不会经常使用,而且您可以通过从基类中删除 virtual 来自己完成。

【讨论】:

  • A 虚拟 b(int),B:公共 A - 无覆盖,C:公共 B 覆盖 b(int),D:公共 C - 无覆盖。在这种情况下,除了编译器之外,你如何表现?哪里应该是vtable,哪里不应该?如果 I B * 实例 = 新 D; ?
  • 另外,如果它被用作插件工厂呢?它没有物理方法可以知道,是否会有覆盖虚拟方法的继承类。
  • 如果在任何地方都有覆盖,则 vtable 必须在任何地方都存在,因为每个类只能有一个版本。只是如果(大如果!)编译器可以确定它是不需要的,它可以被优化出来。
  • 我认为 Als 给出了答案,这只是证明编译器无法确定
【解决方案3】:

编译器无法从“基”类中优化出vtable 成员变量,因为在同一个或另一个项目中可能存在另一个源文件,其中包含以下内容:

struct ived : base {
    ived() : p(new char[BIG_DATA_SIZE]) {}
    virtual ~ived();
    virtual void fn(int);
private:
    char* p;
};

析构函数和fn 可以在其他地方实现:

ived::~ived() { delete[] p; }

void ived::fn(int) {
    cout << "ived" << endl;
}

在另一个地方的某个地方可能会有这样的代码:

base* object = new ived;
ived->fn(0);
delete object;
cout << sizeof(base) << endl;

所以,会有两个问题:虚函数ived::fn没有被调用,虚析构函数没有被调用,所以BIG_DATA_SIZE没有被删除。否则,这里的sizeof(base) 会有所不同。这就是为什么编译器总是为任何具有虚拟成员函数或虚拟基类的类生成vtable

关于在派生类中调用析构函数,必须考虑:如果你有任何带有任何虚函数的类,该类也应该声明一个虚析构函数。

【讨论】:

    【解决方案4】:

    继承是is-a 关系。 der 是一个basebase 的大小为 4der 的大小至少为 4vftableptrbase 的成员,它将是 der 的成员。

    base有一个虚方法,所以不管你有没有用,它都会有一个指向虚表的指针。

    【讨论】:

    • 你没有抓住重点,Q OP 问的是为什么基类的大小是4 而不是为什么派生类的大小是@ 987654333@?
    • 如果没有派生类覆盖虚拟函数
    • @Als 我很困惑,因为这对我来说似乎很简单。它有一个虚方法,为什么它不应该有一个指向 vtable 的指针呢?我编辑了我的答案以反映这一点。
    • @LuchianGrigore:我明白了,只是为了澄清这个问题。OP 说 “如果基类虚拟方法没有被任何派生类覆盖(这意味着不会是根本需要的任何动态调度),那么为什么编译器不优化和消除创建 vtable 的不必要开销?”
    • @Als 好的,我明白了。我看不到编译器知道基类是否被扩展的方法,无论是在当前模块中还是在不同的模块中......
    猜你喜欢
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 2017-12-18
    • 2014-01-15
    • 2013-08-31
    • 2020-06-22
    相关资源
    最近更新 更多