在阅读了许多有关类似主题的文章并思考了一会儿之后,我仍然不明白为什么禁止实现模板虚拟功能。 从我的角度来看,这种情况与静态多态与动态多态无关,而是在编译时使用函数的模板区分,然后在运行时对每个单独创建的函数使用动态多态。时间。
考虑这段代码:
class parrent{
public:
virtual float function(float value)const{
return value;
}
virtual double function(double value)const{
return value;
}
virtual long double function(long double value)const{
return value;
}
virtual ~parrent() = default;
};
class a_child:public parrent{
public:
float function(float value)const override{
return value + 1.5;
}
double function(double value)const override{
return value + 1.5;
}
long double function(long double value)const override{
return value + 1.5;
}
};
显然,此代码可以,并且可以达到预期的结果。 但是使用模板重写类似的代码:
class parrent{
public:
template<typename t__>
virtual t__ function(t__ value)const{
return value;
}
virtual ~parrent() = default;
};
class a_child:public parrent{
public:
template<typename t__>
t__ function(t__ value)const override{
return value + 1.5;
}
};
不允许。
我不是编译器设计者,但是根据我的阅读,编译器将根据虚拟函数创建一个查找表,并使用它们在运行时启动适当的函数,这与模板函数的情况不同。 。对于在编译时给出的使用模板函数的任何模板参数集,编译器将创建一个唯一函数。 对于此示例,编译器可以仅通过查看在整个程序中如何使用此虚拟模板函数来在编译时检测模板参数。请现在考虑主要功能:
int main() {
parrent* a;
parrent* b;
a = new parrent;
b = new a_child;
std::cout<< a->function(1.6f) << std::endl;
std::cout<< a->function(1.6) << std::endl;
std::cout<< a->function(1.6L) << std::endl;
std::cout<< b->function(1.6f) << std::endl;
std::cout<< b->function(1.6) << std::endl;
std::cout<< b->function(1.6L) << std::endl;
delete a;
delete b;
return 0;
}
在这里,编译器将看到该函数一次用于浮点值,一次用于双精度值,一次用于长双精度值,因此在任何情况下,它都可以轻松地使用适当的模板参数创建正确的函数。 最后,将有3个单独的虚拟功能,而不仅仅是一个虚拟功能。 如果我们有一个不能从函数输入中推导出模板参数的函数,例如
template<typename t__>
virtual t__ function(int value){return value;}
然后用户可以自己给参数,例如:
object_pointer->function<double>(1234);
对于任何模板功能,这些做法都是已经使用的方法,所以为什么虚拟功能会有所不同!
我可以想到的唯一警告是,模板虚拟函数是从子对象而不是从父对象或指针实例化的。 好吧,即使在那种情况下,也可以采用相同的做法来创建不同的虚拟功能。另外,由于缺乏虚拟性,它们可以成为正常的个人功能。
从回答和评论中可以看出,这种方法可能存在一个严重的问题,这一点对于其他每个人都是显而易见的,因此请耐心等待,并帮助我理解它。
我猜答案中提到的问题与编译器和/或链接器有关,而后者对于其余代码或其他代码不知道一个类应该生成多少个(和哪种类型的)vtable它可能会遇到的翻译单位。
好吧,可以说它可以生成未完成的vtables列表,并随着它的进行扩展。在使用动态(非模板)函数实例化模板类的情况下,动态链接的情况下,以同一类的两个vtable或两个不同实例结束的问题可能已经发生。 因此,似乎编译器已经有了解决该问题的方法!
首先请不要忘记,对于c而言,方法或类的非静态函数不过是简单函数,这些简单函数要求将对象作为其参数之一,因此,不要将类视为复杂的代码。
其次,我们不要被编译器和链接器的工作方式以及今天不起作用的工作所困扰。该语言应该是标准语言,而不是编译器生成可执行文件的方式!别忘了,标准C ++ 17中仍然有许多功能,甚至GCC都没有涵盖!
请以逻辑的方式向我解释,而不是编译器和/或链接器的工作方式是什么问题?
答案 0 :(得分:4)
编译器实现多态类的方式如下:编译器查看类定义,确定需要多少个vtable条目,并将该vtable中的一个条目静态分配给每个类的虚方法。无论调用这些虚拟方法之一,编译器都会生成代码,该代码从类中检索vptr并在静态分配的偏移量处查找条目,以确定需要调用的地址。
我们现在可以看到拥有虚拟模板将如何引起问题。假设您有一个包含虚拟模板的类。现在,在类定义结束之后,编译器不知道将vtable做成多大。它必须等到翻译单元的末尾才能查看实际调用的模板的专业化的完整列表(或指向成员的指针)。如果仅在该单个转换单元中定义该类,则可以通过以遇到它们的递增顺序将vtable偏移量分配给模板专业化来解决此问题,然后最后发出vtable。但是,如果该类具有外部链接,则该链接会崩溃,因为在编译不同的翻译单元时,编译器无法避免在将偏移量分配给虚拟方法模板的特殊化方面发生冲突。相反,vtable偏移量必须替换为链接器一旦从所有翻译单元中看到所参考专业的列表并将它们合并到一个列表中便会解析的符号。看来,如果标准C ++要求支持虚拟模板,则每个实现都必须要求链接器才能实现此功能。我猜想这很快将不可行。
答案 1 :(得分:1)
我不是编译器设计师,但是我发现您希望执行的操作有问题。
具有虚拟模板成员功能时,例如
template<typename t__>
virtual t__ function(t__ value)const{
return value;
}
适用的类型没有止境。编译器如何知道是否在int
和double
处停止?可以实例化该功能的类型数量不限。您是否希望编译器生成考虑到所有可以实例化函数的方式的vtable?那是无限的。这是不可能的。