原文链接: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的更多相关文章

  1. [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 ...

  2. [CareerCup] 13.3 Virtual Functions 虚函数

    13.3 How do virtual functions work in C++? 这道题问我们虚函数在C++中的工作原理.虚函数的工作机制主要依赖于虚表格vtable,即Virtual Table ...

  3. [译]GotW #5:Overriding Virtual Functions

       虚函数是一个很基本的特性,但是它们偶尔会隐藏在很微妙的地方,然后等着你.如果你能回答下面的问题,那么你已经完全了解了它,你不太能浪费太多时间去调试类似下面的问题. Problem JG Ques ...

  4. Effective C++ Item 35 Consider alternatives to virtual functions

    考虑你正在为游戏人物设计一个继承体系, 人物有一个函数叫做 healthValue, 他会返回一个整数, 表示人物的健康程度. 由于不同的人物拥有不同的方式计算他们的健康指数, 将 healthVal ...

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

  6. 多重继承下的virtual functions

    有如下图所示的继承关系: 有如下代码示例:                   在早期的未符合c++标准的的编译器上是会报错的,因为对于clone()函数来说,编译器不知道怎么处理处理.但是时至今日c ...

  7. 条款35:考虑virtual函数以外的其他选择(Consider alternative to virtual functions)

    NOTE: 1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式.NVI手法自身是一个特殊形式的Template Method设计模式. 2.将机能从成员函数移到外部函 ...

  8. 条款9:绝不在构造和析构过程中调用virtual函数(Never call virtual functions during construction or destruction)

    NOTE:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)

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

随机推荐

  1. Activity界面切换动画特效。

    效果图: 结构图: 测试代码: 布局: 1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearL ...

  2. 反质数(Antiprimes)

    转载http://www.cnblogs.com/tiankonguse/archive/2012/07/29/2613877.html 问题描述: 对于任何正整数x,起约数的个数记做g(x).例如g ...

  3. android自定义控件实例(Linearlayout组合TextView和ImageView)

    2013-12-18 11:25:22 转载自: http://www.open-open.com/lib/view/open1328836804515.html 很多时候android常用的控件不能 ...

  4. ubuntu连接Android调试

    从这周开始尝试Android开发,记下点滴. 安装JDK.下载ADT不说,连接手机调试的时候出错,一堆问号??????????.网上一查,属于典型错误.试下来,有几步比较关键,容易忽视: 1.我机器上 ...

  5. DB2配置信息查看及其更新命令

    获取DB2配置信息 db2 get dbm cfg 更新DB2链接配置信息 db2 update dbm cfg using authentication server db2stop db2star ...

  6. DotNetBar v14.0.0.3 Fully Cracked

    更新信息: http://www.devcomponents.com/customeronly/releasenotes.asp?p=dnbwf&v=14.0.0.3 如果遇到破解问题可以与我 ...

  7. linux的简单网络配置

    1,修改IP edit file: # if rh family system /etc/sysconfig/network-scripts/ifcfg-eth0 (eth0可能会是别的名字) # i ...

  8. [安卓]The Google Android Stack

  9. Introduction to Machine Learning

    Chapter 1 Introduction 1.1 What Is Machine Learning? To solve a problem on a computer, we need an al ...

  10. MongoDB 聚合 (转) 仅限于C++开发

    MongoDB除了基本的查询功能,还提供了很多强大的聚合工具,其中简单的可计算集合中的文档个数, 复杂的可利用MapReduce做复杂数据分析. 1.count count返回集合中的文档数量 db. ...