【发布时间】:2010-11-19 15:27:03
【问题描述】:
我们知道 C++ 不允许在类中使用模板化的虚函数。有人知道为什么会有这样的限制吗?
【问题讨论】:
标签: c++
我们知道 C++ 不允许在类中使用模板化的虚函数。有人知道为什么会有这样的限制吗?
【问题讨论】:
标签: c++
其他答案已经提到,虚函数通常在 C++ 中通过在对象中有一个指向表的指针(vptr)来处理。此表 (vtable) 包含指向用于虚拟成员的函数以及其他一些内容的指针。
解释的另一部分是模板在 C++ 中通过代码扩展来处理。这允许显式特化。
现在,某些语言强制要求(Eiffel - 我认为 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;
}
【讨论】:
简短的回答:虚函数是关于直到在运行时才知道谁调用了谁,此时从已编译的一组候选函数中选择一个函数。函数模板 OTOH 是关于在编译时从调用方创建任意数量的不同函数(使用在编写被调用方时甚至可能不知道的类型)。那就是不匹配。
稍微长一点的答案:虚函数是使用额外的间接实现(程序员的通用万能疗法),通常实现为函数指针表(所谓的虚函数表,通常缩写为“vtable”)。如果您正在调用虚函数,运行时系统将从表中选择正确的函数。如果有虚函数模板,则运行时系统将必须找到已编译的模板实例的地址以及确切的模板参数。由于类的设计器无法提供从无限的可能参数集创建的任意数量的函数模板实例,因此这是行不通的。
【讨论】:
我认为编译器可以将 vtable 偏移量生成为常量(而对非虚函数的引用是修正)。
当你编译一个对模板函数的调用时,编译器通常只是在二进制文件中放一个注释,有效地告诉链接器“请用指向正确函数的指针替换这个注释”。静态链接器做类似的事情,一旦代码被加载到内存并且它的地址是已知的,加载器最终会填充值。这称为修复,因为加载器通过填写所需的数字来“修复”代码。请注意,要生成修复,编译器不需要知道类中还有哪些其他函数,它只需要知道它想要的函数的名称。
但是对于虚函数,编译器通常会发出代码说“从对象中获取 vtable 指针,将 24 添加到它,加载函数地址,然后调用它”。为了知道你想要的特定虚函数在偏移量 24 处,编译器需要知道类中的所有虚函数,以及它们在 vtable 中出现的顺序。就目前情况而言,编译器确实知道这一点,因为所有的虚函数都列在类定义中。但是为了在存在模板化虚函数的地方生成虚调用,编译器需要在调用时知道函数模板有哪些实例化。它不可能知道这一点,因为不同的编译单元可能会实例化不同版本的函数模板。所以它无法确定在 vtable 中使用什么偏移量。
现在,我怀疑编译器可以通过发出整数修正而不是常量 vtable 偏移量来支持虚函数模板。也就是一个注释,上面写着“请用这个munged name填写虚函数的vtable偏移量”。然后,一旦静态链接器知道可用的实例化(在它删除不同编译单元中的重复模板实例化的点),静态链接器可能会填充实际值。但这会给链接器带来沉重的工作负担,以找出 vtable 布局,而目前编译器本身就是这样做的。特意指定模板以使实现者的工作更轻松,希望它们可能在 C++0x 之前的某个时间真正出现在野外......
因此,我推测这些方面的一些推理导致标准委员会得出结论,即使虚拟功能模板完全可以实现,也很难实现,因此不能包含在标准中。
请注意,在我试图解读委员会的想法之前,上面就有不少猜测:我不是 C++ 实现的作者,也不会在电视上播放。
【讨论】:
您将如何构建 vtable?从理论上讲,您可以拥有无限数量的模板化成员版本,并且编译器在创建 vtable 时不会知道它们可能是什么。
【讨论】:
C++'s。