深度探索c++对象模型之member function的具现行为

      对于template 的支持,最困难的就是template function的instatiation【具现】。截至此书问世,大家的编译器提供了两种策略:一个是编译时期策略——模板程序代码必须在program text file【程序文本文件】中备好可用;另一个是编译时起策略,有一些meta-compilation【元编译】工具可以导引编译器的具现行为。

      对于编译器的设计者们,必须要能回答出下面三个问题:

1):编译器如何找出函数的定义?

      答案之一是包含template program text file【模板程序文本文件】,就好像它是个头文件一样,borland编译器就是遵循的这种策略。还有一个方法是要求一种文件命名规则,例如,我们可以规定:在Point.h中发现的函数声明,其相应的template program text file一定要放在文件Point.C或Point.cpp中,以此类推,cfront就是遵循的这种策略。edison design group编译器对这两种策略都支持。

2):编译器如何能够只具现出程序需要用到的函数?

      嗯,这个难度系数很高,所以目前的很多编译器是根本忽略这个问题:把一个已经具现出来的template class的所有member function都具现出来。borland就是这么做的——虽然它也提供#pragmas让用户可以压制一些特定的实体。但还有一种方法——仿真链接操作,检测看一下哪个member function是程序运行时真正需要的,然后只为它或它们产生实体,cfront就是这么做的。edison design group对这两种方法都支持。

3):编译器如何阻止member definitions在多个.o文件中都被具现出来呢?

      有一个办法是产生多个实体,然后从连接器中提供支持,只留下其中一个实体,其余的都忽略。另一个办法是由用户自己来导引“仿真链接阶段”的具现策略,决定哪些instances【实体】才是我们所需求的。

      但不管是编译时期还是链接时期的instantiation【具现】策略,都有一个共同的弱点:当template实体被产生出来时,有时候会大量增加程序的编译时间。显然,这是模板函数第一次具现时的必要条件。然而当这些模板函数被非必要的再次具现,或者当“决定那些模板函数是否需要被再次具现”所花费的代价太大时,编译器的表现令人失望!

      c++支持模板的原始意图是一个由用户导引的use-directed automatic instantiation mechanism【自动具现机制】:既不需要用户的介入,也不需要相同文件有多次的具现行为。但这已经被证明是一个很难的任务,随着cfront3.0版所附加的原始具现工具,提供了一个由用户执行的use-driven automatic instantiation mechanism【自动具现机制】,但它实在太复杂了,即使是一个久经世故的人也没法一下子了解。

      Edison Design Group开发出的一套第二代directed-instantiation【直接具现】机制,非常接近于template facility【模板工具】的原始含义。它的主要过程如下:

1):一个程序的代码被编译时,最初并不会产生任何模板具现体,相关信息被产生于object files之中。

2):当object files被链接到一块时,会有一个prelinker程序被执行,该程序会检查object files,寻找模板实体的相互参考以及对应的定义。

3):对于每一个参考到的模板实体而该实体却没有定义的情况,prelinker会将该文件看作于另一个文件【在这个文件中,实体已经定义】的同类。用这种方法,就可以把必要的程序具现操作指定给特定的文件。这些都会注册在prelinker所产生的.ii文件【放在磁盘目录ii_file】中。

4):prelinker重新执行编译器,重新编译每一个“.ii文件曾被改变过”的文件。这个过程不断重复,直到所有必要的具现操作都已经被完成。

5):所有的object files都被链接成一个可执行问价。

      上面这个directed-instantiation【直接具现】机制的主要成本在于,程序被第一次编译时的.ii文件设定时间;次要成本则是必须针对每一个compile afterwords【后续编译】执行prelinker,以确保所有被参考到的模板们都存在定义。在最初的设定以及成功的第一次链接后,重新编译操作包含以下程序:

1):对于每一个将被重新编译的program text file,编译器会检查其对应的.ii文件。

2):如果这些对应的.ii文件能列出一组要被具现的模板们,那么这些模板将在此次编译时被具现。

3):prelinker必须执行起来,确保所有参考到的模板们都已经被定义好。

      不幸的是,所有的机制都存在一定bugs。Edison Design Group的编译器使用了一个由cfront2.0引入的算法,在大部分情况下,针对程序的每一个class自动产生虚函数表的单一实体,例如下面的class声明:

class PrimitiveObject:public Geometry
{
public:
  virtual ~PrimitiveObject();
  virtual draw();
   ...
}
如果它被包含在几十个源码文件中,编译器如何确保只有一个虚函数表被产生出来呢?倒是产生几十个虚函数表比较容易。

      Andy Koenig【应该是一个c++编译器设计值】用下面的方法解决上面的问题:每一个虚函数的地址都被放置在active classes的虚函数表中,如果取得了函数地址,则意味着虚函数的定义肯定出现在程序的某个地点,否则程序就无法链接成功。此外,该函数只能有一个实体,否则也是链接不成功。那么,就把虚函数表放在定义了该class的第一个non-inline、nonpure virtual function的文件中吧。以我们上面的例子而言,编译器会把虚函数表放在存储着虚析构器的文件之中。

      然而在template之中,这种单一定义并不一定为真,在template所支持的将模块中的每一样东西都编译的模型下,不只是多个定义可能被产生,而且链接器也放任让多个定义同时出现,它只要选择其中一个忽略掉其它的就可以了。

      Edison Design Group机制会做什么事呢?考虑下面这个library函数:

void foo(const Point<float>* ptr)
{
  ptr->virtual_func();
}
其中的虚函数调用会被转换成类似这样的东西:

//c++伪码
//ptr->virtual_func();的转换后形式
(*ptr->_vtbl_Point<float>[2])(ptr);
这会具现出Point类的一个float实体以及它的虚函数func()。由于每一个虚函数的地址被放在虚函数表中,如果虚函数表被产生出来,那么里面的每一个虚函数也必须被具现出来,就像C++ standard所说:如果一个虚拟函数被具现出来,那么它的具现点应紧跟在它所属的class具现点之后。

      然而,如果编译器遵循cfront的虚函数表实现机制,那么在“Point的float实体有一个virtual destructor定义被具现出来”之前,这个虚函数表不会被产生,除非在这一点上,并没有明确使用virtual destructor以担保其具现行为。

      Edison Design Group的automatic template【自动模板】机制并不明确它自己的编译器对于一个non-inline、nonpure virtual function的隐晦使用,所以并没有把它标示于.ii文件中,所以,链接器反而回头抱怨下面这个符号并没有出现:

_vtbl_Point<float>
并拒绝产生一个可执行文件,automatic instantiation在此失效!程序员必须明确的强制将destructor具现出来,目前的编译系统是以(#pragmatic)指令来支持此要求。然而C++ standard也已经扩充了对template的支持,允许程序员明确地要求在一个文件中将整个模板类具现出来:

template class Point3d<float>;
或者针对一个模板类的个别成员函数:

template class Point3d<float>::X() const;
再或者是针对个别模板函数:

template class Point3d<float> operator+(const Point3d<float>&, const Point3d<float>&);
      在实现层面上,template instantiation【模板具现】似乎拒绝全面自动化。甚至虽然每一个工作都做对了,产生出来的object files的重新编译成本还是太高。所以用手动方式先在个别的object module【对象模块】中完成pre-instantiation【预先具现】,虽然沉闷,但也是唯一有效率的办法。





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值