内联虚函数真的是无意义的吗?
当我收到代码审查评论说虚拟函数不需要内联时,我得到了这个问题。
我认为内联虚拟函数在直接在对象上调用函数的场景中会派上用场。 但我想到的反驳是——为什么要定义 virtual 然后使用对象来调用方法?
最好不要使用内联虚拟函数,因为它们几乎永远不会扩展?
我用于分析的代码片段:
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;
}
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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
仅当可以在编译时明确解析调用时,编译器才能内联函数。
然而,虚函数是在运行时解析的,因此编译器无法内联调用,因为在编译类型时无法确定动态类型(因此无法确定要调用的函数实现)。
A compiler can only inline a function when the call can be resolved unambiguously at compile time.
Virtual functions, however are resolved at runtime, and so the compiler cannot inline the call, since at compile type the dynamic type (and therefore the function implementation to be called) cannot be determined.
在函数调用明确且该函数适合内联的情况下,编译器足够聪明,可以内联代码。
其余时间“内联虚拟”是无稽之谈,实际上有些编译器不会编译该代码。
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.
创建虚拟函数然后在对象上调用它们而不是引用或指针确实有意义。 Scott Meyer 在他的《Effective C++》一书中建议,永远不要重新定义继承的非虚拟函数。 这是有道理的,因为当您创建一个具有非虚函数的类并在派生类中重新定义该函数时,您可能确定自己正确使用它,但不能确定其他人会正确使用它。 另外,您以后可能会错误地使用它。 因此,如果您在基类中创建一个函数并且希望它可重新定义,则应该将其设为虚拟。 如果创建虚拟函数并在对象上调用它们有意义,那么内联它们也有意义。
It does make sense to make virtual functions and then call them on objects rather than references or pointers. Scott Meyer recommends, in his book "effective c++", to never redefine an inherited non-virtual function. That makes sense, because when you make a class with a non-virtual function and redefine the function in a derived class, you may be sure to use it correctly yourself, but you can't be sure others will use it correctly. Also, you may at a later date use it incorrectly yoruself. So, if you make a function in a base class and you want it to be redifinable, you should make it virtual. If it makes sense to make virtual functions and call them on objects, it also makes sense to inline them.
实际上,在某些情况下,将“内联”添加到虚拟最终覆盖可能会使您的代码无法编译,因此有时会存在差异(至少在 VS2017s 编译器下)!
实际上,我在 VS2017 中做了一个虚拟内联最终覆盖函数,添加了 c++17 标准来编译和链接,由于某种原因,当我使用两个项目时它失败了。
我有一个测试项目和一个正在单元测试的实现 DLL。 在测试项目中,我有一个“linker_includes.cpp”文件,该文件 #include 来自所需的其他项目的 *.cpp 文件。 我知道...我知道我可以设置 msbuild 来使用 DLL 中的目标文件,但请记住,这是微软特定的解决方案,而包含 cpp 文件与构建系统无关,并且更容易进行版本控制cpp 文件而不是 xml 文件和项目设置等...
有趣的是,我不断从测试项目中收到链接器错误。 即使我通过复制粘贴而不是通过包含添加了缺失函数的定义! 太奇怪了。 另一个项目已经构建,除了标记项目引用之外,两者之间没有任何联系,因此有一个构建顺序来确保始终构建两个项目......
我认为这是编译器中的某种错误。 我不知道它是否存在于 VS2020 附带的编译器中,因为我使用的是旧版本,因为某些 SDK 只能正确使用它:-(
我只是想补充一点,不仅将它们标记为内联可能意味着什么,而且可能甚至让你的代码在某些罕见的情况下无法构建!这很奇怪,但很高兴知道。
PS.:我正在处理的代码与计算机图形相关,所以我更喜欢内联,这就是我同时使用最终和内联的原因。最后的说明符希望发布版本足够智能,即使没有我直接暗示,也可以通过内联来构建 DLL...
PS (Linux).: 我预计 gcc 或 clang 中不会发生同样的情况,就像我通常所做的那样我不知道这个问题从何而来...我更喜欢在 Linux 上使用 c++ 或至少使用一些 gcc,但有时项目的需求有所不同。
Actually in some cases adding "inline" to a virtual final override can make your code not compile so there is sometimes a difference (at least under VS2017s compiler)!
Actually I was doing a virtual inline final override function in VS2017 adding c++17 standard to compile and link and for some reason it failed when I am using two projects.
I had a test project and an implementation DLL that I am unit testing. In the test project I am having a "linker_includes.cpp" file that #include the *.cpp files from the other project that are needed. I know... I know I can set up msbuild to use the object files from the DLL, but please bear in mind that it is a microsoft specific solution while including the cpp files is unrelated to build-system and much more easier to version a cpp file than xml files and project settings and such...
What was interesting is that I was constantly getting linker error from the test project. Even if I added the definition of the missing functions by copy paste and not through include! So weird. The other project have built and there are no connection between the two other than marking a project reference so there is a build order to ensure both is always built...
I think it is some kind of bug in the compiler. I have no idea if it exists in the compiler shipped with VS2020, because I am using an older version because some SDK only works with that properly :-(
I just wanted to add that not only marking them as inline can mean something, but might even make your code not build in some rare circumstances! This is weird, yet good to know.
PS.: The code I am working on is computer graphics related so I prefer inlining and that is why I used both final and inline. I kept the final specifier to hope the release build is smart enough to build the DLL by inlining it even without me directly hinting so...
PS (Linux).: I expect the same does not happen in gcc or clang as I routinely used to do these kind of things. I am not sure where this issue comes from... I prefer doing c++ on Linux or at least with some gcc, but sometimes project is different in needs.
虚函数有时可以内联。 摘自优秀的 C++ 常见问题解答 :
Virtual functions can be inlined sometimes. An excerpt from the excellent C++ faq:
C++11 添加了
final
。 这改变了公认的答案:不再需要知道对象的确切类,知道对象至少具有函数被声明为final的类类型就足够了: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:对于一类虚拟函数,将它们内联仍然有意义。 考虑以下情况:
调用删除'base'时,将执行虚拟调用来调用正确的派生类析构函数,此调用不是内联的。 但是,由于每个析构函数都调用其父析构函数(在这些情况下为空),因此编译器可以内联这些调用,因为它们实际上并不调用基类函数。
对于基类构造函数或派生实现也调用基类实现的任何函数集,也存在相同的原则。
There is one category of virtual functions where it still makes sense to have them inline. Consider the following case:
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 those calls, since they do not call the base class functions virtually.
The same principle exists for base class constructors or for any set of functions where the derived implementation also calls the base classes implementation.
我见过如果根本不存在非内联函数(并且在一个实现文件而不是标头中定义),编译器不会发出任何 v 表。 他们会抛出类似
missing vtable-for-class-A
或类似的错误,你会像我一样感到困惑。事实上,这不符合标准,但它确实发生了,因此请考虑将至少一个虚拟函数不在标头中(如果只是虚拟析构函数),以便编译器可以在该位置为该类发出一个 vtable。 我知道某些版本的
gcc
会发生这种情况。正如有人提到的,内联虚拟函数有时会带来好处,但当然,大多数情况下,当您不知道对象的动态类型时,您会使用它,因为这就是
虚拟
的首要原因。然而,编译器不能完全忽略
内联
。 除了加速函数调用之外,它还有其他语义。 类内定义的隐式内联是一种允许您将定义放入标头中的机制:只有内联
函数可以在整个程序中多次定义,而无需使用违反任何规则。 最后,它的行为就像您在整个程序中只定义了一次一样,即使您将标头多次包含到链接在一起的不同文件中。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-A
or something similar, and you would be confused as hell, as i was.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
.As someone mentioned, inline virtual functions can be a benefit sometimes, but of course most often you will use it when you do not know the dynamic type of the object, because that was the whole reason for
virtual
in the first place.The compiler however can't completely ignore
inline
. It has other semantics apart from speeding up a function-call. The implicit inline for in-class definitions is the mechanism which allows you to put the definition into the header: Onlyinline
functions 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.好吧,实际上虚拟函数总是可以内联,只要它们静态链接在一起:假设我们有一个带有虚拟函数
F
Base的抽象类 code> 和派生类Derived1
和Derived2
:假设调用
b->F();
(使用b
Base* 类型的 > 显然是虚拟的。 但是你(或者编译器...)可以像这样重写它(假设< code>typeof 是一个类似于typeid
的函数,它返回一个可以在switch
中使用的值),而我们仍然需要 RTTI 作为
typeof
,基本上,可以通过将 vtable 嵌入指令流并专门对所有涉及的类进行调用来有效地内联调用。 这也可以通过仅专门化几个类(例如,仅Derived1
)来概括:Well, actually virtual functions can always be inlined, as long they're statically linked together: suppose we have an abstract class
Base
with a virtual functionF
and derived classesDerived1
andDerived2
:An hypotetical call
b->F();
(withb
of typeBase*
) is obviously virtual. But you (or the compiler...) could rewrite it like so (supposetypeof
is atypeid
-like function that returns a value that can be used in aswitch
)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, justDerived1
):内联标记虚拟方法有助于在以下两种情况下进一步优化虚拟函数:
奇怪的重复模板模式(http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurring-Template-Patt)
用模板替换虚拟方法 (http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html)
Marking a virtual method inline, helps in further optimizing virtual functions in following two cases:
Curiously recurring template pattern (http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurring-Template-Patt)
Replacing virtual methods with templates (http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html)
inline 实际上没有做任何事情 - 它只是一个提示。 如果编译器看到实现并喜欢这个想法,它可能会忽略它,或者可能会内联一个没有内联的调用事件。 如果代码清晰度受到威胁,则应删除内联。
inline really doesn't do anything - it's a hint. The compiler might ignore it or it might inline a call event without inline if it sees the implementation and likes this idea. If code clarity is at stake the inline should be removed.
内联声明的虚拟函数在通过对象调用时被内联,而在通过指针或引用调用时被忽略。
Inlined declared Virtual functions are inlined when called through objects and ignored when called via pointer or references.
对于现代编译器来说,嵌入它们不会造成任何损害。 一些古老的编译器/链接器组合可能创建了多个 vtable,但我认为这不再是问题。
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.