我们知道C ++不允许在类中使用模板化虚函数。任何人都明白为什么会有这样的限制?
答案 0 :(得分:12)
简短回答:虚拟函数是在运行时,当从已经编译的候选函数集中选择函数时,不知道是谁调用了谁。函数模板OTOH是关于在编译时从调用者端创建任意数量的不同函数(使用在编写被调用者时可能都不知道的类型)。那只是不匹配。
更长的答案:虚函数是使用额外的间接(程序员的通用多用途治疗)实现的,通常实现为函数指针表(所谓的虚函数表,通常缩写为“vtable”)。如果您正在调用虚函数,则运行时系统将从表中选择正确的函数。如果存在虚函数模板,则运行时系统必须使用确切的模板参数查找已编译的模板实例的地址。由于类的设计者无法提供从无限的可能参数集创建的任意数量的函数模板实例,因此这不起作用。
答案 1 :(得分:10)
你将如何构建vtable?从理论上讲,你可以拥有无限数量的模板化成员版本,编译器在创建vtable时不会知道它们可能是什么。
答案 2 :(得分:4)
其他答案已经提到虚拟函数通常通过在对象中使用指针( 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;
}
答案 3 :(得分:1)
我认为编译器可以生成vtable偏移作为常量(而对非虚函数的引用是修正)。
当您编译对模板函数的调用时,编译器通常只是在二进制文件中放入一个注释,有效地告诉链接器“请用指向正确函数的指针替换此注释”。静态链接器执行类似的操作,一旦代码加载到内存并且其地址已知,最终加载器就会填充值。这称为修复,因为加载器通过填写所需的数字来“修复”代码。请注意,要生成修正,编译器不需要知道类中存在哪些其他函数,只需要知道它想要的函数的名称。
然而,对于虚函数,编译器通常会发出代码,说“将vtable指针从对象中取出,向其中添加24,加载函数地址,然后调用它”。为了知道你想要的特定虚函数是偏移24,编译器需要知道类中的所有虚函数,以及它们将在vtable中出现的顺序。事实上,编译器确实知道这一点,因为所有的虚函数都在类定义中列出。但是为了生成具有模板化虚函数的虚拟调用,编译器需要在调用时知道函数模板的实例。它不可能知道这一点,因为不同的编译单元可能会实例化不同版本的函数模板。所以它无法解决在vtable中使用的偏移量。
现在,我怀疑编译器可以通过发出整数修正而不是常量的vtable偏移来支持虚函数模板。也就是说,请注意“请使用此名称填写虚函数的vtable偏移量”。然后,一旦静态链接器知道可用的实例(在不同的编译单元中删除重复的模板实例),静态链接器就可以填充实际值。但这会给链接器带来严重的工作负担,以找出目前编译器本身所做的vtable布局。故意指定模板以使实现者更容易,希望它们可能在C ++ 0x之前的某个时间实际出现在野外......
因此,我推测这些方面的一些推理促使标准委员会得出结论,虚拟功能模板即使可以实现也难以实现,因此无法包含在标准中。
请注意,在我尝试阅读委员会的思想之前,上面有一些猜测:我不是C ++实现的作者,也不是在电视上播放。