原文链接: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. freemarker实例2

    下面演示一个简单的使用项目过程:1. 使用myeclipse创建一个web项目testFM2. 把下载到的jar包(freemarker-2.3.9.jar)放到/WebRoot/WEB-INF/li ...

  2. windows下mysql主从同步备份步骤

    目的:有两台MySQL数据库服务器A和B,使A为主服务器,B为从服务器,初始状态时,A和B中的数据信息相同,当A中的数据发生变化时,B也跟着发生相应的变化,使得A和B的数据信息同步,达到备份的目的. ...

  3. 类似UC天气下拉和微信下拉眼睛头部弹入淡出UI交互效果(开源项目)。

    Android-PullLayout是github上的一个第三方开源项目,该项目主页是:https://github.com/BlueMor/Android-PullLayout  原作者项目意图实现 ...

  4. 把Angular中的$http变成jQuery.ajax()一样,可以让后台(php)轻松接收到参数

    最近接到一个手机项目,我决定用ionic + php + mysql来实现.ionic是一个前端框架,主要用于手机端,它融合了html5.css3.angularJS于一体,用起来很顺手. 开始构建项 ...

  5. Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use.

    Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use. The ...

  6. web api control注册及重写DefaultHttpControllerSelector、ApiControllerActionSelector、ApiControllerActionInvoker

    namespace EWorkpal.WebApi { public class HttpNotFoundDefaultHttpControllerSelector : DefaultHttpCont ...

  7. ROS主题发布订阅

    节点是一个可执行程序,它连接到了ROS的网络系统中.我们将会创建一个发布者,也就是说话者节点,它将会持续的广播一个信息. 改变目录到之前所建立的那个包下: cd ~/catkin_ws/src/beg ...

  8. 2.4.2电子书fb.c文件

    显示层面头文件 定义结构体,为显示统一标准 int (*DeviceInit)(void); 显示类驱动初始化 int (*ShowPixel)(int iPenX, int iPenY, unsig ...

  9. 神奇的Noip模拟试题 T3 科技节 位运算

    3 科技节 (scifest.pas/.c/.cpp) [问题描述] 一年一度的科技节即将到来.同学们报名各项活动的名单交到了方克顺校长那,结果校长一看皱了眉头:这帮学生热情竟然如此高涨,每个人都报那 ...

  10. DotNetBar v14.0.0.3 Fully Cracked

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