CUJ:标准C++编程:虚函数与内联

原创 2003年03月31日 09:26:00

标准C++编程:虚函数与内联<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Josée Lajoie and Stanley Lippman

----------------------------------------------------------------------------------

[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]

 

曾经,我们常常在谈及C++时听到一个问题:“虚函数真的应该被申明为内联吗?”现在,我们很少再听到这个问题了。反过来,我们现在听到的是“你不应该将print()函数内联。将虚函数申明为内联是错误的。”

这么说有两个主要理由:(1)虚函数是在运行期判决的,而内联是编译期行为,所以不能从这个(内联)申明上得到任何好处;(2)将虚函数申明为内联将造成此函数在可执行文件中有多份拷贝,因此我们为一个无论如何都不能内联的函数付出了在空间上的处罚(WQ注,所谓的内联函数非内联问题)。显然没脑子。

只是它并不真的正确。反思一下理由(1):在很多情况下,虚函数是静态判决的--尤其是派生类的虚函数调用它的基类版本时。为什么会那么做?封装。一个很好的例子是析构函数的静态调用链:基类的析构函数被派生类的析构函数触发。除了最初的一个外,所有的析构函数的调用都是被静态判决的。不让基类的虚析构函数内联,就不能从中获益。这会造成很大的差别吗?如果继承层次很深,而又有大量的对象需要析构,(答案是)“是的”。

另外一个例子不涉及析构函数。想像我们正在设计一个图书馆出借管理程序。我们已经将“位置”放入抽象类LibraryMaterial。当申明print()函数为纯虚函数时,我们也提供其定义:打印出对象的位置。

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 dont 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; }

 

 

接着引入Book类;它的print()函数会输出书名、作者等等。在此之前,它先调用基类的LibraryMaterial::print()函数以显示位置信息。例如:

inline void

Book::

print( ostream &os )

{

      // ok, this is resolved statically,

      // and therefore is inline expanded ...

      LibraryMaterial::print();

       

      os << "title:" << _title

         << "author" << _author << endl;

}

AudioBook类从Book派生,引入了一个二选一的借出策略,并且加入了一些附加信息,比如讲解员、格式等等。这些都将在它的print()函数中显示出来。在显示这些以前,它先调用Book::print():

 

inline void

AudioBook::

print( ostream &os )

{

      // ok, this is resolved statically,

      // and therefore is inline expanded ...

      Book::print();

      os << "narrator:" << _narrator << endl;

}

在这个例子和析构函数的例子中,派生类的虚方法递增式地扩展其基类版本的功能,并以调用链的方式被调用,只有最初一次调用是由虚体系决定的。这个没有被命名的继承树设计模式,如果从不将虚函数申明为内联的话,显然会有些低效。

关于理由(2)的代码膨胀问题怎么说?好吧,思考一下。如果写出,

LibraryMaterial *p =

    new AudioBook( "Mason & Dixon",

                   "Thomas Pynchon", "Johnny Depp" );

// ...

p->print();

此处的print()会内联吗?不,当然不会。这必须在运行期经过虚体系的判决。Okay。它会导致此处的print()函数有它自己的定义体吗?也不会。调用被编译为类似于这种形式:

// Pseudo C++ Code

// Possible transformation of p->print()

( *p->_vptr[ 2 ] )( p );

那个2是print()函数在相应的虚函数表中的位置。因为这个对print()的调用是通过函数指针_vptr[2]进行的,编译器不能静态决定被调用函数的位置,并且函数不能被内联。

当然,内联的虚函数print()的定义必须出现在可执行文件中的某处,代码才能正确执行。也就是说,至少需要一个定义体,以便将它的地址放入虚函数表。编译器如何决定何时产生那一个定义体的呢?一个实现策略是在产生那类的虚函数表时同时产生那个定义体。这意味着针对为一个类所生成的每个虚函数表实例,每个内联的虚函数的一个实例也被产生。

在可执行文件中,为一个类产生的虚函数表,实际上有多少个?啊,很好,问得好。C++标准规定了虚函数在行为上的要求;但它没有规定实现虚函数上的要求。既然虚函数表的存在不是C++标准所要求的,明显标准也没有进一步要求如何处理虚函数表以及生成多少次。最佳的数目当然是“一次”。例如,Stroustrup的原始cfront实现版本,在大部份情况下聪明地达成了这一点。 (Stan和Andy Koenig描述了其算法,发表于1990年3月,C++ Report,Optimizing Virtual Tables in C++ Release 2.0.)

此外,C++标准现在要求内联函数的行为要满足好象程序中只存在一个定义体,即使这个函数可能被定义在不同的文件中。新的规则是说满足规定的实现版本,行为上应该好象只生成了一个实例。一旦标准的这一点被广泛采用,对内联函数潜在的代码膨胀问题的关注应该消失了。

C++社群中存在着一个冲突:教学上需要规则表现为简单的检查表vs实践中需要明智地依据环境而运用规则。前者是对语言的复杂度的回应;后者,是对我们构造的解决方案的复杂度的回应。何时将虚函数申明为内联的问题,是这种冲突的一个很好的例证。

 

About the Authors

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.

 

 

linux内核系统调用和标准C库函数的关系分析

今天研究了一下系统调用和标准库函数的区别和联系,从网上搜集的资料如下: 资料引用分割线 《====================================================...
  • skyflying2012
  • skyflying2012
  • 2013年08月18日 12:44
  • 14055

C++虚函数(10) - 虚函数能否为inline?

虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。而内联函数用于提高效率。内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。内联函数对于程序中需要频繁使用和调用的小函数非常有...
  • shltsh
  • shltsh
  • 2015年05月26日 00:52
  • 1459

c++ 内联函数 (讲解的TM真好)

1.  内联函数 在C++中我们通常定义以下函数来求两个整数的最大值: 复制代码 代码如下: int max(int a, int b) {  return a > b ? a : b;...
  • u011327981
  • u011327981
  • 2016年01月28日 16:49
  • 17483

《C++编程思想》内联,静态成员,引用 笔记

一、内联函数 •保持效率的一个方法是使用宏,但存在两个问题:一是宏看起来像一个函数调用。但并不总是这样。这样就隐藏了难以发现的错误。二是预处器不允许访问类的成员数据,所以引入了内联函数。 •一般地...
  • lzkIT
  • lzkIT
  • 2012年09月23日 20:44
  • 415

C++如何处理内联虚函数

当一个函数是内联和虚函数时,会发生代码替换或使用虚表调用吗? 为了弄清楚内联和虚函数,让我们将它们分开来考虑。通常,一个内联函数是被展开的。 class CFoo{ private: int ...
  • localcpp
  • localcpp
  • 2013年09月15日 22:55
  • 497

C++编程入门系列之四十九(多态性:纯虚函数和抽象类)

上一讲中讲了多态性中的重要概念,虚函数。本节主要讲解另一个软件开发中经常用到的多态概念--抽象类。        抽象类可以为某个类族提供统一的操作接口。外部可以透明的使用抽象类的统一接口,而不...
  • zhaoyinhui0802
  • zhaoyinhui0802
  • 2016年11月22日 16:07
  • 170

《C++编程思想》 第十四章 多态和虚函数 (原书代码+习题+讲解)

一.相关知识点 函数调用捆绑        把函数体与函数调用相联系称为捆绑(binding)。当捆绑在程序运行之前(由编译器和连接器)完成时,称为早捆绑。我们可能没有听到过这个术语,因为在过程语言中...
  • qaz3171210
  • qaz3171210
  • 2015年08月11日 23:20
  • 534

C++编程思想学习笔记---第15章 多态性和虚函数

多态性(polymorphis)提供了接口与具体实现之间的另一层隔离,从而将”what”与”how”分离开来。多态性改善了代码的组织性和可读性,同时也使创建的程序具有可扩展性。 15.1 C++程序员...
  • LucasDove
  • LucasDove
  • 2015年12月30日 18:23
  • 399

C++编程思想杂记(15章 多态性和虚函数)

封装使接口从具体实现中分离开来,而虚函数则根据类型来处理解耦。继承可以使多个类型使用相同的代码,而虚函数可以表现一个类型与其另一个相似类型的区别。 函数体和函数调用相联系称为捆绑,即binding...
  • wdzmswjy1023
  • wdzmswjy1023
  • 2015年03月08日 21:43
  • 225

《C++编程风格》第四章:虚函数

原书抄录: 组件之间的交互关更少并且更简单,将使得程序更容易理解和维护。(低耦合,高内聚) 当我我们要决定在一个类中到底使用数据成员还是函数成员来表示一个特性时,我们首先应该考虑: 这个特性是用属性...
  • luoluozlb
  • luoluozlb
  • 2016年07月28日 18:44
  • 254
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:CUJ:标准C++编程:虚函数与内联
举报原因:
原因补充:

(最多只允许输入30个字)