Templatized Virtual function
我们知道C++在一个类中不允许模板化虚函数。有人知道为什么会有这样的限制吗?
简短的回答:虚拟函数是在运行时从已经编译好的候选函数集中选择一个函数时才知道是谁调用的。函数模板(otoh)是关于在编译时从调用方端创建任意数量的不同函数(使用编写被调用方时甚至可能不知道的类型)。只是不匹配。
回答稍微长一点:虚拟函数是使用一个额外的间接(程序员的通用通用的通用方法)来实现的,通常作为函数指针表(所谓的虚拟函数表,通常缩写为"vtable")来实现。如果您正在调用一个虚拟函数,运行时系统将从表中选择正确的函数。如果存在虚拟函数模板,则运行时系统必须使用准确的模板参数查找已编译模板实例的地址。由于类的设计器无法提供从无限可能参数集创建的任意数量的函数模板实例,因此无法工作。
您将如何构造vtable?理论上,您可以拥有无限多的模板成员版本,编译器在创建vtable时不知道它们可能是什么。
其他的答案已经提到,虚函数通常在C++中通过在对象中有一个指向表的指针(VPTR)来处理。此表(vtable)包含指向要用于虚拟成员的函数的指针以及其他一些内容。
解释的另一部分是通过代码扩展在C++中处理模板。这允许显式的专门化。
现在,一些语言授权(Effel-我认为它也是Java和C语言的情况,但是我对它们的知识不够好,不能权威)或者允许(艾达)共享泛型处理,没有明确的专门化,但是允许虚拟模板函数,将模板放入库中,并可以减少代码的大小。
您可以使用一种称为类型擦除的技术来获得共享泛型的效果。这是手动执行的,共享泛型语言的编译器正在执行的操作(好吧,至少其中一些,取决于语言,其他实现技术可能是可能的)。下面是一个(愚蠢的)例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #include <string.h> #include <iostream> #ifdef NOT_CPP class C { public: virtual template<typename T> int getAnInt(T const& v) { return getint(v); } }; #else class IntGetterBase { public: virtual int getTheInt() const = 0; }; template<typename T> class IntGetter: public IntGetterBase { public: IntGetter(T const& value) : myValue(value) {} virtual int getTheInt() const { return getint(myValue); } private: T const& myValue; }; template<typename T> IntGetter<T> makeIntGetter(T const& value) { return IntGetter<T>(value); } class C { public: virtual int getAnInt(IntGetterBase const& v) { return v.getTheInt(); } }; #endif int getint(double d) { return static_cast<int>(d); } int getint(char const* s) { return strlen(s); } int main() { C c; std::cout << c.getAnInt(makeIntGetter(3.141)) + c.getAnInt(makeIntGetter("foo")) << ' '; return 0; } |
我认为这是因为编译器可以将vtable偏移量生成为常量(而对非虚拟函数的引用是修正值)。
当编译对模板函数的调用时,编译器通常只在二进制文件中放一个注释,有效地告诉链接器"请用指向正确函数的指针替换这个注释"。静态链接器做了类似的事情,当代码加载到内存中并且其地址已知时,加载程序最终填充该值。这被称为修正,因为加载程序通过填充所需的数字来"修正"代码。注意,为了生成修正,编译器不需要知道类中还有什么其他函数,只需要知道它想要的函数的名称。
然而,对于虚拟函数,编译器通常会发出代码,表示"从对象中取出vtable指针,向其添加24个,加载一个函数地址,然后调用它"。为了知道您想要的特定虚函数位于偏移量24处,编译器需要知道类中的所有虚函数,以及它们在vtable中的显示顺序。事实上,编译器确实知道这一点,因为所有的虚拟函数都列在类定义中。但是,为了在有模板化虚拟函数的地方生成虚拟调用,编译器需要知道在调用点,函数模板有什么样的实例化。它不可能知道这一点,因为不同的编译单元可能会实例化函数模板的不同版本。所以它无法计算出在vtable中使用的偏移量。
现在,我怀疑编译器可以通过发出一个整数修正值来支持虚拟函数模板,而不是发出一个常量vtable偏移量。也就是说,一个注释说"请用这个被咀嚼的名称填写虚拟函数的vtable偏移量"。然后,静态链接器可能会在知道哪些实例化可用后填充实际值(此时它会删除不同编译单元中的重复模板实例化)。但是,这将给链接器带来一个很严重的负担,使其无法找出vtable布局,而当前编译器自己就是这么做的。模板被故意指定以使实现者更容易,希望在C++0X之前的某个时候它们可能真的出现在野外。
因此,我推测,按照这些思路进行的一些推理导致标准委员会得出结论,虚拟功能模板即使完全可以实现,也太难实现,因此不能包含在标准中。
请注意,在我试图阅读委员会的思想之前,上面有一些推测:我不是C++实现的作者,也不在电视上播放。