【问题标题】:Does a base class method marked as `virtual final` introduce additional overhead?标记为“virtual final”的基类方法是否会引入额外的开销?
【发布时间】:2020-03-06 11:48:00
【问题描述】:

假设我想要一个类Base 有两种方法:foo(int)bar(int)。我希望它们被定义为:

  • Base::foo 必须在派生的非抽象类中被覆盖
  • Base::bar 不能在派生类中被覆盖

第一个目标可以通过将foo标记为virtual int foo(int) = 0来实现,使其抽象化。第二个要求可以通过将bar标记为virtual int bar(int) final使其成为最终要求来满足。这是生成的代码:

class Base
{
public:
    virtual int foo(int n) = 0;
    virtual int bar(int n) final
    {
        return n + 42;
    }
};

还有一个派生自Base的示例类:

class Derived : public Base
{
public:
    virtual int foo(int n) override
    {
        return n * n;
    }
    int bar(int n) // compilation error here
    {
        return n + 43;
    }
};

如我们所愿,试图覆盖 Base::bar 已触发编译错误。

现在,我的问题是:将方法标记为 virtual final 是否会因为函数为 virtual(动态调度)而引入开销,即使该函数无论如何都不能被覆盖?

编辑

不要介意缺少虚拟析构函数~Base(),它不是为了缩短代码。

【问题讨论】:

  • @Evg 已修复 facepalm
  • @PeteBecker 但是Derived 可以覆盖它,不是吗?
  • 隐藏,而不是覆盖。
  • 我猜唯一真正的开销是vtable中的函数指针,编译器应该能够优化它
  • "不能在派生类中被覆盖" 那么为什么要使用 virtual 呢?如果不是virtual,则不能覆盖它。

标签: c++ inheritance methods overriding virtual-functions


【解决方案1】:

在您的情况下没有开销,因为您的类没有从任何类继承定义您的最终函数,因此编译器将生成对其地址的直接调用,而不管虚拟声明。

这是来自实际代码:

b->FoA(); //virtual inherited
002829A7  mov         eax,dword ptr [b]  
002829AA  mov         edx,dword ptr [eax]  
002829AC  mov         esi,esp  
002829AE  mov         ecx,dword ptr [b]  
002829B1  mov         eax,dword ptr [edx]  
002829B3  call        eax  
002829B5  cmp         esi,esp  
002829B7  call        __RTC_CheckEsp (02812E9h)  
    b->FoB(); // final
002829BC  mov         ecx,dword ptr [b]  
002829BF  call        A::FoB (0281366h)  

【讨论】:

  • 感谢您的回答。您能否提供您的信息来源(最好是标准的引用)?
  • @user234057356 标准没有规定特定的 impl 方法。
【解决方案2】:

编译器可能会去虚拟化这个调用:

struct Base {
    virtual int bar(int n) final {
        return n + 42;
    }
};

struct Derived : Base { };

int foo(Derived& d, int n) {
    return d.bar(n);
}

becomes-O1

foo(Derived&, int):
        lea     eax, [rsi+42]
        ret

而没有final,我们会得到一个间接调用:

foo(Derived&, int):
        sub     rsp, 8
        mov     rax, QWORD PTR [rdi]
        call    [QWORD PTR [rax]]
        add     rsp, 8
        ret

【讨论】:

  • 感谢您的回答。你知道标准的要求编译器这样做还是只是允许
  • AFAIK,编译器不必这样做。
  • @YanB。该标准没有具体说明虚函数如何在幕后工作,或者它们的工作速度。 (对于大多数其他语言功能也是如此。)
猜你喜欢
  • 2020-10-09
  • 2017-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多