AKL*_*AKL 2 c++ virtual templates function
在阅读了许多类似主题的帖子并思考了一段时间后,我仍然不明白为什么禁止实现模板虚函数。在我看来,这种情况与混合静态多态性与动态多态性无关,而是在编译时使用函数的模板区分,然后在运行时对每个单独创建的函数使用动态多态性。时间。
考虑这段代码:
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;
}
};
Run Code Online (Sandbox Code Playgroud)
显然这段代码是没问题的,能够达到预期的效果。但使用模板重写类似的代码:
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;
}
};
Run Code Online (Sandbox Code Playgroud)
不允许。
我不是编译器设计者,但根据我所读到的内容,编译器将从虚拟函数创建一个查找表,并使用它们在运行时启动适当的函数,这与它们在模板函数中所做的不同。对于在编译时指定使用模板函数的任何模板参数集,编译器将创建一个唯一的函数。对于此示例,编译器只需查看整个程序中如何使用该虚拟模板函数即可在编译时检测模板参数。现在请考虑主要功能:
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;
}
Run Code Online (Sandbox Code Playgroud)
在这里,编译器将看到该函数一次用于浮点值,一次用于双精度值,一次用于长双精度值,因此在任何情况下,它都可以使用适当的模板参数轻松创建正确的函数。最终将会有 3 个单独的虚拟函数,而不仅仅是 1 个虚拟函数。如果我们有一个函数,其模板参数无法从函数输入中推断出来,例如
template<typename t__>
virtual t__ function(int value){return value;}
Run Code Online (Sandbox Code Playgroud)
然后用户可以自己给出参数,例如:
object_pointer->function<double>(1234);
Run Code Online (Sandbox Code Playgroud)
这些做法正是任何模板函数中已经使用的做法,那么为什么虚拟函数会有所不同呢!
我能想到的这种做法的唯一警告是,模板虚函数从子对象实例化,而不是从父对象或指针实例化。即使在这种情况下,也可以应用相同的实践来创建不同的虚拟功能。或者,由于缺乏对其虚拟性的使用,它们可以成为正常的个人功能。
从答案和评论来看,这种方法可能存在一个严重的问题,这对其他人来说都是显而易见的,所以请耐心等待并帮助我理解它。
我想答案中提到的问题与编译器和/或链接器无法知道它应该为一个类生成多少(以及什么类型)虚函数表有关,涉及其余代码或不同的翻译单元它可能会面临。
好吧,假设它可以生成一个未完成的 vtable 列表,并随着它的进行而扩展它。在动态链接的情况下,使用虚拟(非模板)函数实例化模板类时,可能会出现最终出现两个 vtable 或同一类的两个不同实例的问题。看来编译器已经有办法规避这个问题了!
首先,不要忘记,对于 c,方法或类非静态函数只不过是需要对象作为其参数之一的简单函数,因此我们不要将类视为某些复杂的代码。
其次,我们不要被编译器和链接器如何工作以及今天不工作的东西所迷惑。语言应该是标准的,而不是编译器生成可执行文件的方式!我们不要忘记,标准 c++ 17 中还有许多甚至 GCC 都没有涵盖的功能!
请用逻辑而不是编译器和/或链接器的工作方式向我解释问题是什么?
编译器实现多态类的方式如下:编译器查看类定义,确定需要多少个 vtable 条目,然后将该 vtable 中的一个条目静态分配给该类的每个虚拟方法。无论何时调用这些虚拟方法之一,编译器都会生成从类中检索 vptr 的代码,并在静态分配的偏移量处查找条目,以确定需要调用的地址。
我们现在可以看到虚拟模板会如何导致问题。假设您有一个包含虚拟模板的类。现在,在类定义结束后,编译器不知道要创建多大的 vtable。它必须等到翻译单元结束,才能查看实际调用的模板特化的完整列表(或指向成员的指针)。如果该类仅在该单个翻译单元中定义,则可以通过以下方式解决此问题:按遇到的升序将 vtable 偏移量分配给模板特化,然后在最后发出 vtable。但是,如果类具有外部链接,则会出现这种情况,因为在编译不同的翻译单元时,编译器无法避免为虚拟方法模板的特化分配偏移量时发生冲突。相反,vtable 偏移量必须替换为符号,一旦链接器看到来自所有翻译单元的引用专业化列表并将它们合并到单个列表中,链接器就会解析这些符号。看起来,如果标准 C++ 需要支持虚拟模板,那么每个实现都必须要求链接器来实现此功能。我可以猜测这在短期内是不可能实现的。