来自Addison Wesley:C ++模板
成员函数模板不能 声明虚拟。这个约束是 因为通常的强加 虚函数的实现 调用机制使用固定大小的表 每个虚拟功能一个条目。 但是,实例化的数量 成员函数模板的不是 固定,直到整个程序 已被翻译。
以上引用是否意味着模板具有静态绑定,虚拟功能具有动态绑定,这就是无法使用虚拟功能模板的原因?请查看是否可以用外行的语言进行解释。
答案 0 :(得分:24)
是的,没有。
解决虚函数调用最常用的方法是使用表(“vtable”),其中每个虚函数映射到表中的索引。这或多或少要求您知道表格的大小。
使用模板,将根据需要在不同模块中创建新功能。然后,您必须说服链接器在确定最终的函数数量后构建表,或者使用某种运行时结构在运行时搜索可用的函数。
在许多系统上,链接器是操作系统的一部分,对C ++一无所知,因此该选项有限。运行时搜索当然会对性能产生负面影响,可能对所有虚函数都有影响。
所以,最后,我们决定将虚拟模板引入语言并不值得。
答案 1 :(得分:7)
考虑:
struct X
{
template <typename T>
T incr(const T& t)
{
return t + 1;
}
};
当incr()
应用于不同的T类型时,会生成新函数。在app.c++
里面说:
X x;
x.incr(7); // incr<int>()
x.incr(7.0); // incr<double>()
x.incr("hello"); // incr<const char*>()
然后,当它正在编译app.c++
时,它会看到3个函数 - 如果incr
被允许为virtual
- 它可以为虚拟调度表中的上述三个实例创建空间X.然后说它在运行时加载一个共享库,该库的代码有X::incr
uint32_t
和std::string::const_iterator
的2个瞬时。 dlopen()
需要为已创建的对象增加现有的虚拟调度表,以便为两个新函数腾出空间。听起来不太可怕,但请考虑一下:
调用虚函数的每一段代码都必须知道这些函数的地址是否在运行时被一些偏移(由于动态加载额外的实例化)所致,所以每个都有额外的内存和性能成本虚拟调度
当存在多个继承,或者派生类本身是派生自的时,编译器可能希望为整个虚拟函数集创建单个虚拟调度表(一个选项,有许多用于实现虚拟调度) :在这种情况下,新的虚函数要么取代其他类的虚函数,要么与现有的虚函数脱节。同样,在任何方案中都有更多的运行时间开销来管理它。
因此,非常罕见的情况下这可能是有用的,不值得妥协,并使非模板虚拟的更常见情况复杂化。
答案 2 :(得分:4)
上面的引用是否意味着模板具有静态绑定,虚拟功能具有动态绑定,这就是没有虚拟功能模板的原因?
基本上,是的。更具体地说,静态绑定在生成代码以支持动态绑定时会导致问题。
当编译器编译基类时,它会找到一个虚函数并决定创建一个虚函数表 - 这将用于实现动态绑定:当在派生实例上调用虚函数时,编译后的代码遵循实例中的指针指向派生类的虚函数表,然后在该表中指针执行虚函数。该表必须包括可以调用的每个可能的虚函数。现在,假设我们制作了一个模板化的虚函数。函数表需要为模板的每个实例化提供一个条目,因为可以想象在运行时调用这些函数中的任何一个。但是,关于模板实例化的类型的信息,通常不能在生成虚拟功能表时收集。 (至少,不是没有使用C ++编译模型。)
答案 3 :(得分:1)
虚拟功能和模板仍然可以很好地协同工作,只有一个小特例,没有实现。
template<class T>
class A { virtual void f()=0; }; // works fine
class A { template<class T> virtual void f(T t)=0; }; // does not work
答案 4 :(得分:0)
取决于你的意思是绑定。
您可以通过调用成员模板来实现虚拟方法。只要你内联它,任何具有尾调用优化的编译器都将消除开销
答案 5 :(得分:0)
八九不离十。
您无法真正“覆盖”未实例化的template
,因为它甚至不存在于已编译的应用程序中。如果你实例化它,那么你不是覆盖模板,而是覆盖另一个普通函数。 : - )