禁止模板虚拟功能是否是一种不必要的谨慎
Is forbidding template virtual functions an unnecessary cautiousness?
看了很多类似话题的帖子,思考了一段时间,还是不明白为什么禁止实现模板虚函数。 在我看来,这种情况与将静态多态性与动态多态性混合无关,而是在编译时使用函数的模板微分,然后在运行时对每个单独创建的函数使用动态多态性。
考虑这段代码:
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 列表并随着时间的推移对其进行扩展。在动态链接的情况下,使用虚拟(非模板)函数实例化模板类时,最终会得到两个 vtables 或同一类的两个不同实例的问题已经发生了。 所以编译器似乎已经有办法规避这个问题了!
首先,我们不要忘记,对于c,方法或类非静态函数只不过是需要对象作为其参数之一的简单函数,因此我们不要将类视为一些复杂的代码片段。
其次,我们不要被编译器和链接器以及今天不起作用的东西所迷惑。语言应该是标准的,而不是编译器生成可执行文件的方式!我们不要忘记,标准 c++ 17 中仍有许多功能甚至 GCC 还没有涵盖!
请从逻辑方面向我解释,而不是编译器和/或链接器的工作方式是什么问题?
编译器实现多态类的方式如下:编译器查看类定义,确定需要多少个 vtable 条目,并将该 vtable 中的一个条目静态分配给类的每个虚拟方法。无论在哪里调用这些虚拟方法之一,编译器都会生成从类中检索 vptr 的代码,并在静态分配的偏移量处查找条目,以确定需要调用的地址。
我们现在可以看到拥有虚拟模板会导致问题。假设您有一个包含虚拟模板的类。现在,在类定义结束后,编译器不知道要使 vtable 有多大。它必须等到翻译单元结束,才能看到实际调用的模板专用化的完整列表(或指向成员的指针)。如果类只在这个单一的翻译单元中定义,则可以通过将 vtable 偏移量按遇到的递增顺序分配给模板专用化,然后在末尾发出 vtable,从而解决此问题。但是,如果类具有外部链接,则会崩溃,因为在编译不同的翻译单元时,编译器无法避免在将偏移量分配给虚拟方法模板的专用化时发生冲突。相反,vtable 偏移量必须替换为符号,链接器在看到所有翻译单元的引用专业列表并将它们合并到单个列表中后,这些符号将由链接器解析。似乎如果标准C++需要支持虚拟模板,则每个实现都必须要求链接器来实现此功能。我可以猜测,这在短期内是不可行的。
我不是编译器设计师,但我看到您希望做的事情有问题。
当您具有虚拟模板成员函数时,例如
template<typename t__>
virtual t__ function(t__ value)const{
return value;
}
适用的类型没有尽头。编译器如何知道是否在int
和double
停止?可以实例化该函数的类型数量不受限制。您是否希望编译器生成考虑函数实例化的所有可能方式的 vtable?那是无限的。这是不可行的。
- 在调用接收数组的方法时,模板化数组大小是不是一种糟糕的做法
- 将错误返回给调用方而不是立即在 C++ 中抛出错误是否是一种好的做法
- C++,您能否设计一种数据结构,将指针保存在连续内存中并且不会使它们失效?
- 在C++中使用变量而不是"#define"来指定数组大小是不是一种糟糕的做法?(C错误:在文件范围内
- 有没有一种通用的方法来实现不变量
- 有没有一种惯用的方法可以在不存储变换或不必要地重新计算的情况下找到数组变换的最小/最大值?
- C++|以一种很好的方式将树(不一定是二进制的)打印到stdout
- 有没有一种优雅的方法可以使用向量修改器并获得新的向量,而不是更改原始向量
- 在C++中,有没有一种方法可以让我在不传递参数的情况下拥有一个函数
- 这是河内算法的递归塔是一种不知情的搜索
- 禁止模板虚拟功能是否是一种不必要的谨慎
- 有没有一种不太直截了当的方法将基类方法引入子类?
- 将代码片段转换为另一种语言 不必要的 = OP
- 两种不同的解分配向量的方法为什么一种不起作用?
- 如何使用数组通过引用对对象进行排序?(以一种不那么愚蠢/复杂的方式.)
- C++11是否引入了一种不区分大小写的字符串比较算法
- 连接(以一种不寻常的方式)信号到插槽
- 有没有一种不使用std::getline从文件中读取空格的方法?
- 一种不使用XAML关闭DirectX Metro应用程序的方法
- 有没有更好的方法来组合静态库而不携带一堆不必要的代码?