Standard C++ Programming: Virtual Functions and Inlining
原文链接:http://www.drdobbs.com/cpp/standard-c-programming-virtual-functions/184403747
By Josée Lajoie and Stanley Lippman, September 01, 2000
As we gain mastery of C++, it is natural to question the rules of thumb that helped us get by in the beginning. Lippman and Lajoie provide an excellent example of such a rule regarding virtual functions, and why that rule should be carefully reconsidered.
[This is the last installment of a column that was being published in C++ Report magazine. Since the magazine ceased publication before this installment could be published, Josée Lajoie and Stan Lippman were gracious enough to let us publish it on the CUJ website. — mb]
At one time, a common question that we used to hear when speaking on C++ was, "Should a virtual function really be declared inline?" These days, we hardly ever hear that question. Rather, what we hear now is "You shouldn't have made print inline. It's wrong to declare a virtual function inline."
The two primary reasons for taking this position are (1) virtual functions are resolved at run-time while the inline facility is a compile-time mechanism, so there is nothing to be gained by the declaration; and (2) declaring a virtual function inline results in multiple copies of the function being defined within our executable, so we pay a space penalty for a function that can't be inlined in any case. An obvious no-brainer.
Only that's not really true. Let's take item (1) first: there are many cases in which a virtual function is resolved statically — essentially any time a derived class virtual method invokes the method of its base class(es). Why would one do that? Encapsulation. A good example is the static invocation chain of base class destructors triggered by the virtual resolution of a derived class destructor. All the destructor calls except for the initial resolution are resolved statically. Without making the base class virtual destructors inline, we cannot take advantage of this. Does it make much of a difference? If the hierarchy is deep and there are many objects destructed, yes.
For another example that does not use destructors, imagine that we are designing a library lending material hierarchy. We've factored the material's location into the abstract LibraryMaterial class. While we declare its print function as a pure virtual function, we also provide a definition: it prints out the material's location.
- class LibraryMaterial {
- private:
- MaterialLocation _loc; // shared data
- // ...
- public:
- // declares pure virtual function
- inline virtual void print( ostream& = cout ) = 0;
- };
- // we actually want to encapsulate the handling of the
- // location of the material within a base class
- // LibraryMaterial print() method - we just don't want it
- // invoked through the virtual interface. That is, it is
- // only to be invoked within a derived class print() method
- inline void
- LibraryMaterial::
- print( ostream &os ) { os << _loc; }
Next we introduce a Book class; its print function outputs the title, author, and so on. Before this, it invokes the base class LibraryMaterial::print function to display the location information. For example,
- inline void
- Book::
- print( ostream &os )
- {
- // ok, this is resolved statically,
- // and therefore is inline expanded ...
- LibraryMaterial::print();
- os << "title:" << _title
- << "author" << _author << endl;
- }
Our AudioBook class, derived from Book, introduces an alternative lending policy, and adds additional information such as narrator, format, and so on. These are displayed in its print function. Before this, it invokes Book::print(), which in turn, etc:
- inline void
- AudioBook::
- print( ostream &os )
- {
- // ok, this is resolved statically,
- // and therefore is inline expanded ...
- Book::print();
- os << "narrator:" << _narrator << endl;
- }
In both this example and the example of the class destructor, the virtual method of the derived class incrementally expands on the functionality of its base class and involves a chain of invocations where only the initial invocation is resolved virtually. This unnamed hierarchical design pattern is significantly less effective if we never declare a virtual function to be inline.
What about the potential of code bloat cited in item (2)? Well, let's think about that. If we write,
- LibraryMaterial *p =
- new AudioBook( "Mason & Dixon",
- "Thomas Pynchon", "Johnny Depp" );
- // ...
- p->print();
is this instance of print to be inlined? No, of course not. This has to be resolved at run-time through the virtual mechanism. Okay. Does it cause this instance of print to have its definition laid down? Also no. The call is transformed into something of the form:
- // Pseudo C++ Code
- // Possible transformation of p->print()
- ( *p->_vptr[ 2 ] )( p );
where 2 represents the location of print within the associated virtual function table. Because this call to print is done through the function pointer _vptr[2], the compiler cannot statically determine the location of the called function, and the function cannot be inlined.
Of course the definition of the inline virtual print function must appear somewhere in the executable for the code to run properly. That is, at least one definition is necessary in order for its address to be placed within the virtual table. How does the compiler decide when to generate that definition? One implementation strategy is to generate that definition at the same time the class virtual table is generated. That means that for each virtual table instance generated for a class, an instance of each inline virtual function is also generated.
Just how many virtual tables are actually generated within an executable for a class? Ah, well, that's a good question. The Standard makes requirements on the behavior of virtual functions; it does not make requirements on their implementation. Since the presence of a virtual table is not required by the Standard, obviously the Standard in turn makes no requirements on how the virtual table is handled or how many are generated. The optimal number, of course, is one. Stroustrup's original cfront implementation, for example, achieved that in most cases through cleverness. (Stan and Andy Koenig described the algorithm in the March 1990 C++ Report article, "Optimizing Virtual Tables in C++ Release 2.0.")
Moreover, the C++ Standard now requires that inline functions behave as though only one definition for an inline function exists in the program even though the function may be defined in different files. The new rule is that conforming implementations should behave as though only a single instance is generated. Once this aspect of the Standard is widely implemented, lingering concerns over potential code bloat from inline functions should disappear.
One of the tensions in the C++ community is the pedagogical need to present a simple checklist set of rules versus the practical need to apply rules judiciously based on the situational context. The former is a response to the complexity of the language; the latter, to the complexity of the solutions we need to construct. The problem of when to declare virtual functions inline is a good illustration of this tension.
Stanley Lippman was the software Technical Director for the Firebird segment of Disney's Fantasia 2000. He was recently technical lead on the ToonShooter image capture and playback system under Linux for DreamWorks Feature Animation and consulted with the Jet Propulsion Laboratory. He is currently IT Training Program Chair for You-niversity.com, an e-learning training company. He can be reached at stanleyl@you-niversity, www.you-niversity.com, and www.objectwrite.com.
Josée Lajoie is currently doing her Master's degree in Computer Graphics at the University Waterloo. Previously, she was a member of the C/C++ compiler development team at the IBM Canada Laboratory and was the chair of the core language working group for the ANSI/ISO C++ Standard Committee. She can be reached at jlajoie@cgl.uwaterloo.ca.
Standard C++ Programming: Virtual Functions and Inlining的更多相关文章
- [C++] OOP - Virtual Functions and Abstract Base Classes
Ordinarily, if we do not use a function, we do not need to supply a definition of the function. Howe ...
- [CareerCup] 13.3 Virtual Functions 虚函数
13.3 How do virtual functions work in C++? 这道题问我们虚函数在C++中的工作原理.虚函数的工作机制主要依赖于虚表格vtable,即Virtual Table ...
- [译]GotW #5:Overriding Virtual Functions
虚函数是一个很基本的特性,但是它们偶尔会隐藏在很微妙的地方,然后等着你.如果你能回答下面的问题,那么你已经完全了解了它,你不太能浪费太多时间去调试类似下面的问题. Problem JG Ques ...
- Effective C++ Item 35 Consider alternatives to virtual functions
考虑你正在为游戏人物设计一个继承体系, 人物有一个函数叫做 healthValue, 他会返回一个整数, 表示人物的健康程度. 由于不同的人物拥有不同的方式计算他们的健康指数, 将 healthVal ...
- Effective C++ Item 9 Never call virtual functions during constrution or destruction
Because such calls would never go to a more derived class than that of currently executing construto ...
- 多重继承下的virtual functions
有如下图所示的继承关系: 有如下代码示例: 在早期的未符合c++标准的的编译器上是会报错的,因为对于clone()函数来说,编译器不知道怎么处理处理.但是时至今日c ...
- 条款35:考虑virtual函数以外的其他选择(Consider alternative to virtual functions)
NOTE: 1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式.NVI手法自身是一个特殊形式的Template Method设计模式. 2.将机能从成员函数移到外部函 ...
- 条款9:绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruction)
NOTE:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
- R Programming week2 Functions and Scoping Rules
A Diversion on Binding Values to Symbol When R tries to bind a value to a symbol,it searches through ...
随机推荐
- Linux 中 10 个有用的命令行补全例子
在 Linux 系统中,当你输入一个命令,再按两次 TAB 键,就会列出所有以你输入字符开头的可用命令.这并不新鲜,可能你已经知道了.这个功能被称作命令行补全bash completion.默认情况下 ...
- js基础之事件
一.event对象 document.onclick=function(ev){ oEvent=event?event:ev;//兼容性写法 alert(oEvent.clientX); alert( ...
- asp.net mvc 2.o 中使用JQuery.uploadify
From:http://www.cnblogs.com/strugglesMen/archive/2011/07/01/2095916.html 官方网站http://www.uploadify.co ...
- c# 读取excel 出现数字读取成“”空
读取excel用到的方法: /// <summary> /// Excel导入数据源 /// </summary> /// <param name="sheet ...
- 无刷新 checkbox列表的删除
前台 JS : function ModelDelete() { var checkvalues = null; var checValue = $("#dom1").find(& ...
- Java 多线程间的通讯
在前一小节,介绍了在多线程编程中使用同步机制的重要性,并学会了如何实现同步的方法来正确地访问共享资源.这些线程之间的关系是平等的,彼此之间并不存在任何依赖,它们各自竞争CPU资源,互不相让,并且还无条 ...
- VMware-workstation-full-10.0.3-1895310 CN
Name: VMware-workstation-full-10.0.3-1895310.exe发行日期: 2014-07-01内部版本号: 1895310文件大小: 491 MB文件类型: exe ...
- Windows Azure 实操 —— 迁移本地SharePoint服务器到Azure
博客地址 http://blog.csdn.net/foxdave 注意:如果你是第二代虚拟机,那就别看这个了,老老实实在Azure上重新创建吧,Azure不支持第二代虚拟机. 写在之前,对Azure ...
- XCode之entitlement
entitlement是codesign的一个输入,参见:codesign. entitlement的意思是权力,也就是表明应用所具有的权利,可以访问什么,不能访问什么等.这些信息会在codesign ...
- Xcode如何查看内存中的数据
在 debug 模式下如何在断点处,查看字符指针变量内存中的值,像vs2008的调试工具一样的内存查看器,现在只能查看第一个内存中的值可以在输出窗口采用gdb命令:x /nfu <addr&g ...