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 ...
随机推荐
- 0302IT行业就业&软件工程之我所思和所想
阅读以下文章 http://www.thea.cn/news/terminal/9/9389.html http://www.shzhidao.cn/system/2015/09/22/0102610 ...
- Ubuntu14.10+cuda7.0+caffe配置
转自:http://blog.csdn.net/lu597203933/article/details/46742199 Ubuntu14.10+cuda7.0+caffe配置 一:linux安装 L ...
- Java并发编程(三)后台线程(Daemon Thread)
后台线程,守护线程(Daemon Thread) 所谓的后台线程,就是指这种线程并不属于程序中不可或缺的部分,因此当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程.通过setD ...
- python多线程与多进程
由于python的内存回收机制不是线程安全的,所以就有了GIL保证每个进程内,同一时刻最多只有一个线程在运行. 于是,对于python的多线程来讲,其实同一时刻依然只有一个线程在运行.而且由于线程切换 ...
- English test for certificate
<英语口译全真试题精解> GPA 3.5 (MTI) (CPA) ( GRE ) 1350分+3.5以上 SAT ( TOEFL ) 100分以上 TOEIC BEC (剑桥商务英语 ...
- primitive数据类型
/*primitive数据类型 *primitive主要是用来存储原始的数据 *boolean\byte\short\int\long\double **/public class Shujuleix ...
- ios 8+ (xcode 6.0 +)应用程序Ad Hoc 发布前多设备测试流程详解
我们开发的程序在经过simulator以及自己的iOS设备测试后,也基本完成应用程序了,这时候我们就可以把它发布出去了更更多的人去测试,我们可以在iOS平台使用ad hoc实现. 你在苹果购买的开发者 ...
- ODI 11g & 12c中缓慢变化维(SCD)的处理机制
缓慢变化维(Slowly changing Dimensions)指的是维表中的维度字段值会随着时间或业务调整,而在后续的分析中,历史数据仍然要使用旧的维度值,新的数据会使用当前维度值.在数据仓库建设 ...
- 第四课 Gallery的使用
直接上代码 1.Layout--Main.axml <?xml version="1.0" encoding="utf-8"?> <Linea ...
- android studio只能全部提示设置