C++ 内联虚函数真的没有意义吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/733737/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 16:59:31  来源:igfitidea点击:

Are inline virtual functions really a non-sense?

c++inlinevirtual-functions

提问by aJ.

I got this question when I received a code review comment saying virtual functions need not be inline.

当我收到一条代码评论说虚拟函数不需要内联时,我得到了这个问题。

I thought inline virtual functions could come in handy in scenarios where functions are called on objects directly. But the counter-argument came to my mind is -- why would one want to define virtual and then use objects to call methods?

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

Is it best not to use inline virtual functions, since they're almost never expanded anyway?

最好不要使用内联虚函数,因为它们几乎从不扩展?

Code snippet I used for analysis:

我用于分析的代码片段:

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;
}

采纳答案by ya23

Virtual functions can be inlined sometimes. An excerpt from the excellent C++ faq:

有时可以内联虚拟函数。摘自优秀的C++ 常见问题

"The only time an inline virtual call can be inlined is when the compiler knows the "exact class" of the object which is the target of the virtual function call. This can happen only when the compiler has an actual object rather than a pointer or reference to an object. I.e., either with a local object, a global/static object, or a fully contained object inside a composite."

“内联虚拟调用可以被内联的唯一时间是当编译器知道作为虚拟函数调用目标的对象的“确切类”时。只有当编译器具有实际对象而不是指针或对对象的引用。即,可以使用本地对象、全局/静态对象或组合中的完全包含对象。”

回答by MSalters

C++11 has added final. This changes the accepted answer: it's no longer necessary to know the exact class of the object, it's sufficient to know the object has at least the class type in which the function was declared final:

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.
}

回答by Richard Corden

There is one category of virtual functions where it still makes sense to have them inline. Consider the following case:

有一类虚函数让它们内联仍然有意义。考虑以下情况:

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
}

The call to delete 'base', will perform a virtual call to call correct derived class destructor, this call is not inlined. However because each destructor calls it's parent destructor (which in these cases are empty), the compiler can inline thosecalls, since they do not call the base class functions virtually.

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

The same principle exists for base class constructors or for any set of functions where the derived implementation also calls the base classes implementation.

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

回答by Johannes Schaub - litb

I've seen compilers that don't emit any v-table if no non-inline function at all exists (and defined in one implementation file instead of a header then). They would throw errors like missing vtable-for-class-Aor something similar, and you would be confused as hell, as i was.

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

Indeed, that's not conformant with the Standard, but it happens so consider putting at least one virtual function not in the header (if only the virtual destructor), so that the compiler could emit a vtable for the class at that place. I know it happens with some versions of gcc.

事实上,这不符合标准,但它发生了,所以考虑在头文件中至少放置一个虚拟函数(如果只有虚拟析构函数),以便编译器可以在该位置为该类发出一个 vtable。我知道某些版本的gcc.

As someone mentioned, inline virtual functions can be a benefit sometimes, but of course most often you will use it when you do notknow the dynamic type of the object, because that was the whole reason for virtualin the first place.

正如有人提到的,内联虚函数有时会带来好处,但当然大多数情况下,当您知道对象的动态类型时,您会使用它,因为这首先是全部原因virtual

The compiler however can't completely ignore inline. It has other semantics apart from speeding up a function-call. The implicit inlinefor in-class definitions is the mechanism which allows you to put the definition into the header: Only inlinefunctions can be defined multiple times throughout the whole program without a violation any rules. In the end, it behaves as you would have defined it only once in the whole program, even though you included the header multiple times into different files linked together.

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

回答by CAFxX

Well, actually virtual functions can always be inlined, as long they're statically linked together: suppose we have an abstract class Basewith a virtual function Fand derived classes Derived1and Derived2:

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

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

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

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

An hypotetical call b->F();(with bof type Base*) is obviously virtual. But you (or the compiler...) could rewrite it like so (suppose typeofis a typeid-like function that returns a value that can be used in a switch)

一个假设的调用b->F();b类型为Base*)显然是虚拟的。但是您(或编译器...)可以像这样重写它(假设typeof是一个类似typeid函数,它返回一个可以在 a 中使用的值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)
}

while we still need RTTI for the typeof, the call can effectively be inlined by, basically, embedding the vtable inside the instruction stream and specializing the call for all the involved classes. This could be also generalized by specializing only a few classes (say, just Derived1):

虽然我们仍然需要 的 RTTI typeof,但可以有效地内联调用,基本上,将 vtable 嵌入到指令流中,并将调用专门用于所有涉及的类。这也可以通过只专门化几个类(例如,只是Derived1)来概括:

switch (typeof(b)) {
  case Derived1: b->Derived1::F(); break; // hot path
  default:       b->F(); break; // default virtual call, cold path
}

回答by Chenna Reddy

Marking a virtual method inline, helps in further optimizing virtual functions in following two cases:

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

回答by tarachandverma

Inlined declared Virtual functions are inlined when called through objects and ignored when called via pointer or references.

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

回答by sharptooth

inlinereally doesn't do anything - it's a hint. The compiler might ignore it or it might inline a call event without inlineif it sees the implementation and likes this idea. If code clarity is at stake the inlineshould be removed.

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

回答by sharptooth

With modern compilers, it won't do any harm to inlibe them. Some ancient compiler/linker combos might have created multiple vtables, but I don't believe that is an issue anymore.

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

回答by moonshadow

In the cases where the function call is unambiguous and the function a suitable candidate for inlining, the compiler is smart enough to inline the code anyway.

在函数调用明确且函数适合内联的情况下,编译器足够聪明,可以内联代码。

The rest of the time "inline virtual" is a nonsense, and indeed some compilers won't compile that code.

其余时间“内联虚拟”是无稽之谈,确实有些编译器不会编译该代码。