2009-08-14 166 views

回答

10

简短回答:虚拟功能是关于在运行时不知道谁叫谁,当从已经编译好的候选函数中选取一个函数时。函数模板OTOH是在编译时从调用者的两端创建任意数量的不同函数(使用在被调用者编写时甚至可能不知道的类型)。这只是不匹配。稍微长一些的答案:虚拟功能是通过使用额外的间接方式(程序员的通用多用途治疗)来实现的,通常作为功能指针表(所谓的虚拟功能表,通常缩写为“vtable”)来实现。如果你正在调用一个虚函数,运行时系统将从表中选择正确的函数。如果有虚拟功能模板,则运行系统必须使用确切的模板参数来查找已编译的模板实例的地址。由于类的设计者不能提供任意数量的由无限可能的参数创建的函数模板实例,所以这是行不通的。

9

你会如何构建vtable?理论上讲,你可以拥有无​​限数量的模板化成员版本,编译器在创建vtable时不知道它们会是什么。

+0

最近downvoted这个人的任何机会也许足够勇敢地说为什么? :) – Troubadour 2009-08-24 20:53:39

+0

Vtable是一个实现细节,在设计语言特性时应该考虑到后端的困难 - 至少,“C++”。 – 2009-09-06 15:51:10

+1

Pavel:很抱歉把它给你,但这就是为什么它被禁止。如果你不相信我,请尝试阅读Stroustrup的“The C++ Programming Language”。 – Troubadour 2009-09-06 16:39:24

1

我认为编译器可以生成vtable偏移量作为常量(而对非虚拟函数的引用是修正)。

当您编译对模板函数的调用时,编译器通常只是在二进制文件中放置一个注释,有效地告诉链接器“请用正确函数的指针替换此注释”。静态链接器做了类似的事情,一旦代码加载到内存中并且地址已知,加载器最终会填充该值。这被称为fixup,因为加载程序通过填充所需的数字来“修复”代码。请注意,为了生成fixup,编译器不需要知道该类中还有其他什么函数,只需知道它想要的函数的名称即可。

但是,对于虚拟函数,编译器通常会发出代码,指出“将vtable指针从对象中取出,向其中添加24,加载函数地址并调用它”。为了知道你想要的特定虚拟函数的偏移量为24,编译器需要知道类中的所有虚函数,以及它们将在vtable中出现的顺序。就目前而言,编译器确实知道这一点,因为所有的虚拟函数都在类定义中列出。但是为了在模板化的虚函数中生成虚拟调用,编译器需要在调用时知道函数模板的实例。它不可能知道这一点,因为不同的编译单元可能会实例化函数模板的不同版本。所以它无法确定在vtable中使用的偏移量。

现在,我怀疑编译器可以通过发出一个整数修正而不是一个常量vtable偏移来支持虚拟函数模板。也就是说,请注意“请填写虚拟函数的vtable偏移量”。然后,静态链接器可能会在知道哪些实例化可用时(在删除不同编译单元中的重复模板实例的地方)填充实际值。但是这会给连接器带来沉重的工作负担,无法找出vtable的布局,目前编译器自己做。模板是特意指定的,以便让实现者更容易,希望它们可能在C++ 0x之前实际出现在野外...

因此,我推测这些方面的一些推理导致标准委员会得出结论,虚拟功能模板即使可以实现,也很难实现,因此无法包含在标准中。

请注意,即使在我尝试阅读委员会的意见之前,上面也有一些猜测:我不是C++实现的作者,也不是我在电视上播放的。

4

其他答案已经提到虚拟函数通常用C++处理,方法是在对象中指向一个表的指针(vptr)。此表(vtable)包含指向用于虚拟成员的函数的指针以及其他一些内容。

解释的另一部分是通过代码扩展在C++中处理模板。这允许明确的专业化。 (埃菲尔 - 我认为这也是Java和C#的情况,但是我的知识不足以成为权威),或者允许(Ada)共同处理通用性,不要没有明确的专业化,但会允许虚拟模板功能,将模板放入库中并可减少代码大小。

您可以使用称为类型擦除的技术来获得共享通用性的效果。这是手动执行共享通用性语言的编译器正在做什么(至少,其中一些取决于语言,其他实现技术可能是可能的)。这是一个(愚蠢)的例子:

#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")) << '\n'; 
    return 0; 
} 
+1

提及类型擦除+1 – sbi 2009-08-14 19:45:43