Programming 版 (精华区)

发信人: zhangyan (电梯吃人啊), 信区: Programming
标  题: 标准C++编程:虚函数与内联
发信站: 哈工大紫丁香 (2001年05月15日12:34:08 星期二), 站内信件


标准C++编程:虚函数和内联
  
    有一段时间,我们经常听到的关于C++的问题是"这个虚函数是否应该声明为内联"。
最近,我们很难听到这个问题。我们现在听到的是:"你不应当令print为内联。声明一
个内联的需函数是错误的。"
  
    我们采纳这一观点主要有两个原因(1)虚函数是在运行时间确定的而内联函数特性
是一个编译时刻的机制;(2)声明一个内联的虚函数会导致再程序执行的时间的产生多
个函数拷贝,所以我们为一个不能为内联的函数付出了空间的代价。很明显这很不明智。

  只有这些是不正确的。让我们看看下面的条款(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 don抰 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(),依次如下

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实例是内联的吗?不,当然不是。这要通过虚函数机制在运行时刻确定。
好吧,这个print实例放弃它的定义了吗?也不是,这一调用被转换为类似于下面的一些
东西:
// Pseudo C++ Code
// Possible transformation of p->print()
( *p->_vptr[ 2 ] )( p );
  
    2代表print在虚函数列表中的位置。因为这个printf的调用通过函数指针_vptr[2]
来实现,编译器不能再编译时刻确定调用函数的地址,所以函数不可为内联。
  
    当然,内联虚函数print的定义必须在某个地方出现以保证执行代码调用的恰当的运
行。也就是,至少需要一个定义来在虚函数列表中放置它的地址。编译器如何确定在什
么时候生成那个定义呢?一个方法是在虚函数列表生成的时候就生成定义。这意味着为
每个类的实例生成一个虚函数列表。每一个内联函数的实例也同时产生。
  
    在一个可执行程序中为一个类要生成多少虚函数列表呢?这是一个好问题。标准对
虚函数的行为做了一些规定;但是没有队实现做出约束。因为虚函数列表没有在标准中
做出规定。所以明显也不会去规定如何控制虚函数列表或者生成多少实例。最理想的数
目,当然是一个。拿Stroustrup的cfront的原始实现为例,聪明的在许多案例中实现了
这点。
  
    此外,C++标准现在要求内联函数表现得好象在一个程序中只有一个内联函数的定义
即使函数是在不同的文件中定义的。新的规定是使实现表现为只有一个实例产生。当标
准的这个特性被广泛实现的时候涉及到代码膨胀的潜在问题也将会消失。
 

--
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.170.172]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:5.666毫秒