`
yiyeqinghuasoon
  • 浏览: 643515 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

我们曾经在讨论C++的时候,经常会问到:“虚函数能被声明为内联吗?”现在,我们几乎听不到这个问题了。现在听到的是:“你不应该使print成为内联的。声明一个虚函数为内联是错误的!”

这种说法的两个主要的原因是(1)虚函数是在运行期决议而内联是一个编译期动作,所以,我们将虚函数声明为内联并得不到什么效果;(2)声明一个虚函数为内联导致了函数的多分拷贝,而且我们为一个不应该在任何时候内联的函数白白花费了存储空间。这样做很没脑子。

不过,事实并不是这样。我们先来看看第一个:许多情况下,虚拟函数都被静态地决议了——比如在派生类虚拟函数中调用基类的虚拟函数的时候。为什么这样做 呢?封装。一个比较明显的例子就是派生类析构函数调用链。所有的虚析构函数,除了最初触发这个析构链的虚析构函数,都被静态的决议了。如果不将基类的虚析 构函数内联,我们无法从中获利[a]。这和不内联一个虚拟析构函数有什么不同吗?如果继承体系层次比较深并且有许多这样的类的实例要被销毁的话,答案是肯 定的。

再来看另外一个不用析构函数的例子,想象一下设计一个图书馆类。我们将MaterialLocation作为抽象类LibraryMaterial的一个成员。将它的print成员函数声明为一个纯虚函数,并且提供函数定义:它输出MaterialLocation。

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

接着,我们引入一个Book类,它的print函数输出Title, Author等等。在这之前,它调用基类的print函数(LibraryMaterial::print())来显示书本位置(MaterialLocation)。如下:

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;

}

这和虚析构函数调用链的例子一样,都只是最初调用的虚函数没有被静态决议,其它的都被原地展开。This unnamed hierarchical design pattern is significantly less effective if we never declare a virtual function to be inline.

那么对于第二个原因中代码膨胀的问题呢?我们来分析一下,如果我们写下:

LibraryMaterial *p =

new AudioBook( "Mason & Dixon",

"Thomas Pynchon", "Johnny Depp" );

// ...

p->print();

这个print实例是内联的吗?不,当然不是。这样不得不通过虚拟机制在运行期决议。这让print实例放弃了对它的内联声明了吗?也不是。这个调用转换为下面的形式(伪代码):

// 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.因为调用print是通过函数指针_vptr[2]进行的,所以,编译器不能静态的决定这个调用地址,并且,这个函数也不能内联。

当然,虚函数print的内联实体(definition)也必须在某个地方表现出来。 即是说,至少有一个函数实体是在virtual table调用的地址原地展开的。编译器是如何决定在何时展开这个函数实体呢?其中一个编译(implementaion)策略是当virtual table生成的同时,生成这个函数实体。这就是说对于每一个派生类的virtual table都会生成一个函数实体。

在一个可应用的类[b]中有多少vitrual table会被生成呢?呵呵,这是一个好问题。C++标准中对虚函数行为进行了规定,但是没有对函数实现进行规定。由于virtual table没有在C++标准中进行规定,很明显,究竟这个virtual table怎样生成,和究竟要生成多少个vitrual table也没有规定。多少个?当然,我们只要一个。Stroustrup的cfront编译器,很巧妙的处理了这些情况.( 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。新的规则要求编译器只展开一个内联虚函数。如果一点被广泛采用的话,虚函数的内联导致的代码膨胀问题就会消失。

[译注:C++ Standard: 9.3.8, Member function of local class shall be defined inline in their class defination, if they are defined at all]

============================

译注:

[a]函数调用开销,调用基类虚函数的时候至少要经过两次间接过程(S. B.Lippman: 《Inside the C++ Object Model》)

[b]一个产品类(?)

总结:

就是虚函数inline在调用链等地方很有用~

即使没有加入inline声明,作为一个好编译器,都会优化(虚析构函数)

在很长的函数调用链中,最好将链中基类的函数inline,这样节约开销

至于在什么地方inline,由编译器决定,因为C++标准没有规定

新C++标准(可能没有通过)中,规定了,inline化只对产品类有效,且只动作一次

保证代码不过度膨胀

inline动作是在产品类实例化同时,和vtable生成一起

分享到:
评论

相关推荐

    鸡啄米:C++编程入门系列

    鸡啄米:C++编程入门系列之四十八(多态性:虚函数) 鸡啄米:C++编程入门系列之四十九(多态性:纯虚函数和抽象类) 第九部分:异常处理 鸡啄米:C++编程入门系列之五十(异常处理) 鸡啄米:C++编程入门系列...

    C++编程思想(Thinking in C++)完美版pdf

    第15章 多态性和虚函数 第16章 模板介绍 附录A 编码风格 附录B 编程准则 附录C 推荐读物 索引 第2卷:实用编程技术 出版者的话 专家指导委员会 译者序 前言 第一部分 建立稳定的系统 第1章 异常处理 第2章 防御性...

    c++编程思想 本书作为正式教材和自学用书均非常优秀,作为程序设计者的参考用书亦极为合适。

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    c++编程思想c++编程思想<第二版>

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++ 编程思想 象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器类、多重继承、异常处理和运行时类型识别

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    c++编程思想-用简单的例子和简练的叙述讲解C++编程

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想之第一卷

    第1卷:标准C++导引 目录 译者序 前言 第1章 对象导言 第2章 对象的创建与使用 第3章 C++中的C 第4章 数据抽象 ...第15章 多态性和虚函数 第16章 模板介绍 附录A 编码风格 附录B 编程准则 附录C 推荐读物 索引

    C + +编程思想 c++的编程思想

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    Thinking in C++中文版(C++编程思想)

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想,进一步理解c++

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想 C++编程思想 C++编程思想

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    c++编程思想 内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想.pdf

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    c++编程思想,c++实例编程

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    c++编程学习及快速入门

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    Thinking C++ 编程思想 中文版

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想亲身体会

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想(第11-21章)

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++ 编程思想中文版

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想,很经典的,pdf格式

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

Global site tag (gtag.js) - Google Analytics