内联虚拟功能真的没有意义吗?

时间:2009-04-09 11:03:52

标签: c++ inline virtual-functions

当我收到代码审核评论时,我收到了这个问题,说虚拟功能不需要内联。

我认为在直接在对象上调用函数的场景中,内联虚函数可以派上用场。但是我想到的反驳论点是 - 为什么要定义虚拟然后使用对象来调用方法呢?

最好不要使用内联虚拟功能,因为它们几乎从未扩展过吗?

我用于分析的代码段:

class Temp
{
public:

    virtual ~Temp()
    {
    }
    virtual void myVirtualFunction() const
    {
        cout<<"Temp::myVirtualFunction"<<endl;
    }

};

class TempDerived : public Temp
{
public:

    void myVirtualFunction() const
    {
        cout<<"TempDerived::myVirtualFunction"<<endl;
    }

};

int main(void) 
{
    TempDerived aDerivedObj;
    //Compiler thinks it's safe to expand the virtual functions
    aDerivedObj.myVirtualFunction();

    //type of object Temp points to is always known;
    //does compiler still expand virtual functions?
    //I doubt compiler would be this much intelligent!
    Temp* pTemp = &aDerivedObj;
    pTemp->myVirtualFunction();

    return 0;
}

13 个答案:

答案 0 :(得分:138)

有时可以内联虚拟功能。优秀C++ faq

的摘录
  

“唯一一次内联虚拟呼叫   可以内联的是编译时   知道对象的“确切类”   这是虚拟的目标   功能调用。这种情况只会发生   当编译器有一个实际的对象时   而不是指针或引用   一个东西。即,与当地人一起   对象,全局/静态对象或   一个完全包含的对象   复合材料“。

答案 1 :(得分:69)

C ++ 11添加了final。这改变了接受的答案:不再需要知道对象的确切类,只要知道对象至少具有声明final的函数的类类型就足够了:

class A { 
  virtual void foo();
};
class B : public A {
  inline virtual void foo() final { } 
};
class C : public B
{
};

void bar(B const& b) {
  A const& a = b; // Allowed, every B is an A.
  a.foo(); // Call to B::foo() can be inlined, even if b is actually a class C.
}

答案 2 :(得分:36)

有一类虚拟功能,让它们内联仍然有意义。考虑以下情况:

class Base {
public:
  inline virtual ~Base () { }
};

class Derived1 : public Base {
  inline virtual ~Derived1 () { } // Implicitly calls Base::~Base ();
};

class Derived2 : public Derived1 {
  inline virtual ~Derived2 () { } // Implicitly calls Derived1::~Derived1 ();
};

void foo (Base * base) {
  delete base;             // Virtual call
}

删除'base'的调用,将执行虚拟调用以调用正确的派生类析构函数,此调用未内联。但是因为每个析构函数都调用它的父析构函数(在这些情况下为空),编译器可以内联那些调用,因为它们不会虚拟地调用基类函数。

基类构造函数或派生实现也调用基类实现的任何函数集都存在相同的原则。

答案 3 :(得分:14)

如果根本没有非内联函数存在(并且在一个实现文件中定义,而不是在头文件中定义),我看到没有发出任何v表的编译器。他们会抛出missing vtable-for-class-A或类似的错误,你会像我一样被混淆。

实际上,这与标准不一致,但实际上考虑将至少一个虚函数放在标题中(如果只是虚析构函数),以便编译器可以在该位置为类发出vtable。我知道它发生在某些版本的gcc

正如有人提到的,内联虚拟功能有时可以 ,但是当你知道对象的动态类型时,通常会使用它,因为这是virtual首先的全部原因。

然而,编译器无法完全忽略inline。除了加速函数调用之外,它还有其他语义。用于类内定义的隐式内联是允许您将定义放入标题的机制:在整个程序中只能定义多次inline函数而不违反任何规则。最后,它的行为与您在整个程序中只定义一次一样,即使您将标题多次包含在链接在一起的不同文件中。

答案 4 :(得分:10)

嗯,实际上虚拟函数总是可以内联,只要它们静态链接在一起:假设我们有一个带有虚函数Base的抽象类F和派生类Derived1Derived2

class Base {
  virtual void F() = 0;
};

class Derived1 : public Base {
  virtual void F();
};

class Derived2 : public Base {
  virtual void F();
};

虚拟电话b->F();b类型为Base*)显然是虚拟的。但是你(或compiler ...)可以像这样重写它(假设typeof是一个类似typeid的函数,它返回一个可以在{{1}中使用的值})

switch

虽然我们仍然需要switch (typeof(b)) { case Derived1: b->Derived1::F(); break; // static, inlineable call case Derived2: b->Derived2::F(); break; // static, inlineable call case Base: assert(!"pure virtual function call!"); default: b->F(); break; // virtual call (dyn-loaded code) } 的RTTI,但基本上,可以通过在指令流中嵌入vtable并专门调用所有相关类的调用来有效地内联调用。这也可以通过仅专门化几个类(例如,只是typeof)来概括:

Derived1

答案 5 :(得分:3)

内联确实没有做任何事情 - 这是一个提示。编译器可能会忽略它,或者它可能在没有内联的情况下内联调用事件,如果它看到实现并喜欢这个想法。如果代码清晰度受到威胁,则应删除 inline

答案 6 :(得分:3)

内联声明的虚函数在通过对象调用时内联,在通过指针或引用调用时被忽略。

答案 7 :(得分:3)

将内联虚拟方法标记,有助于在以下两种情况下进一步优化虚函数:

答案 8 :(得分:1)

使用现代编译器,它们不会对它们造成任何伤害。一些古老的编译器/链接器组合可能已经创建了多个vtable,但我不认为这是一个问题。

答案 9 :(得分:1)

在函数调用明确且函数适合内联的情况下,编译器足够聪明,无论如何都要内联代码。

剩下的时间“内联虚拟”是一个废话,实际上有些编译器不会编译该代码。

答案 10 :(得分:0)

编译器只能在编译时明确解析调用时内联函数。

然而,虚函数在运行时被解析,因此编译器无法内联调用,因为在编译类型时无法确定动态类型(因此要调用的函数实现)。

答案 11 :(得分:0)

创建虚函数然后在对象而不是引用或指针上调用它们是有意义的。 Scott Meyer在他的“有效的c ++”一书中建议永远不要重新定义一个继承的非虚函数。这是有道理的,因为当你创建一个具有非虚函数的类并在派生类中重新定义函数时,你可能确定自己正确使用它,但你不能确定其他人会正确使用它。此外,您可能在以后使用它错误yoruself。因此,如果您在基类中创建一个函数并且希望它可以重新编写,那么您应该将其设置为虚拟。如果制作虚拟函数并在对象上调用它们是有意义的,那么内联它们也是有意义的。

答案 12 :(得分:0)

实际上,在某些情况下,将“内联”添加到虚拟最终替代中可能会使您的代码无法编译,因此有时会有所不同(至少在VS2017s编译器下)!

实际上,我在VS2017中做一个虚拟内联最终覆盖函数,添加了c ++ 17标准进行编译和链接,由于某种原因,当我使用两个项目时,它失败了。

我有一个测试项目和一个我正在进行单元测试的实现DLL。在测试项目中,我有一个“ linker_includes.cpp”文件,该文件#include需要的其他项目中的* .cpp文件。我知道...我知道我可以设置msbuild以使用DLL中的目标文件,但是请记住,这是Microsoft特定的解决方案,而包含cpp文件与构建系统无关,并且更容易版本化一个cpp文件,而不是xml文件和项目设置等。

有趣的是,我不断从测试项目中收到链接器错误。即使我通过复制粘贴(而不是通过包含)添加了缺少功能的定义!太奇怪了。另一个项目已经构建,除了标记项目引用之外,两者之间没有任何联系,因此有一个构建顺序来确保始终都构建这两个项目。

我认为这是编译器中的某种错误。我不知道它是否存在于VS2020附带的编译器中,因为我使用的是旧版本,因为某些SDK仅可以正确地使用它:-(

我只是想补充一点,不仅将它们标记为内联可能意味着某些事情,甚至可能使您的代码在极少数情况下无法构建!这很奇怪,但很高兴知道。

PS .:我正在处理的代码与计算机图形相关,所以我更喜欢内联,这就是为什么我同时使用final和inline的原因。我保留了最后的说明符,希望发行版本足够聪明,即使没有我直接暗示也可以通过内联它来构建DLL。

PS(Linux)。:我希望在gcc或clang中不会发生与我通常用来做这类事情的情况相同的情况。我不确定这个问题的来源...我更喜欢在Linux上或至少在某些gcc上使用c ++,但是有时项目的需求有所不同。