首页 > 解决方案 > 禁止模板虚函数是不必要的谨慎吗?

问题描述

看了很多类似话题的帖子,想了想,还是不明白为什么禁止实现模板虚函数。在我看来,这种情况与将静态多态性与动态多态性混合无关,而是在编译时使用函数的模板微分,然后在运行时对每个单独创建的函数使用动态多态性-时间。

考虑这段代码:

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;
}

在这里,Compiler 将看到该函数被用于浮点值,一次用于双精度值,一次用于长双精度值,因此在任何情况下,它都可以轻松地使用适当的模板参数创建正确的函数。最终会有3个单独的虚函数,而不仅仅是一个虚函数。如果我们有一个无法从函数输入中推断出模板参数的函数,例如

template<typename t__>
virtual t__ function(int value){return value;}

然后用户可以自己给出参数,例如:

object_pointer->function<double>(1234);

这些实践正是在任何模板函数的情况下已经使用的,那么为什么虚拟函数会有所不同!

我能想到的对这种做法的唯一警告是,模板虚函数是从子对象实例化的,而不是从父对象或指针实例化的。好吧,即使在那种情况下,也可以应用相同的做法来创建不同的虚拟功能。或者,由于缺乏使用它们的虚拟性,它们可以成为正常的个人功能。

从答案和评论看来,这种方法可能存在一个严重的问题,这对其他人来说是显而易见的,所以请耐心等待并帮助我理解它。

我猜答案中提到的问题与编译器和/或链接器无法知道它应该为一个类相对于其余代码或不同翻译单元生成多少(和什么类型)vtables有关它可能面临。

好吧,可以说它可以生成一个未完成的 vtables 列表并随着它的进行扩展它。在实例化具有虚拟(非模板)函数的模板类时,可能已经发生在动态链接的情况下以两个 vtable 或同一类的两个不同实例结束的问题。所以看来编译器已经有了规避这个问题的方法!

首先不要忘记,对于 c,方法或类的非静态函数只不过是需要一个对象作为其参数之一的简单函数,所以不要将类视为一些复杂的代码。

其次,让我们不要被编译器和链接器的方式以及今天不起作用的东西所迷惑。该语言应该是标准的,而不是编译器生成可执行文件的方式!让我们不要忘记标准 c++ 17 中还有许多甚至 GCC 都没有涵盖的特性!

请用逻辑而不是编译器和/或链接器的工作方式向我解释有什么问题?

标签: c++functiontemplatesvirtual

解决方案


编译器实现多态类的方式如下:编译器查看类定义,确定需要多少个 vtable 条目,并将该 vtable 中的一个条目静态分配给每个类的虚拟方法。无论在哪里调用这些虚拟方法之一,编译器都会生成从类中检索 vptr 的代码,并在静态分配的偏移量处查找条目以确定需要调用的地址。

我们现在可以看到拥有虚拟模板会如何导致问题。假设您有一个包含虚拟模板的类。现在,在类定义结束后,编译器不知道要制作多大的 vtable。它必须等到翻译单元结束,才能查看实际调用的模板特化的完整列表(或指向成员的指针)。如果类只在这个单一的翻译单元中定义,这个问题可以通过将 vtable 偏移量分配给模板特化以某种递增的顺序遇到它们,然后在最后发出 vtable 来解决。但是,如果该类具有外部链接,则会发生故障,例如在编译不同的翻译单元时,编译器无法避免在将偏移量分配给虚拟方法模板的特化时发生冲突。相反,一旦链接器看到所有翻译单元的引用特化列表并将它们合并到一个列表中,就必须用链接器解析的符号替换 vtable 偏移量。似乎如果标准 C++ 需要支持虚拟模板,那么每个实现都必须要求链接器来实现这个功能。我可以猜测这在短期内是不可行的。似乎如果标准 C++ 需要支持虚拟模板,那么每个实现都必须要求链接器来实现这个功能。我可以猜测这在短期内是不可行的。似乎如果标准 C++ 需要支持虚拟模板,那么每个实现都必须要求链接器来实现这个功能。我可以猜测这在短期内是不可行的。


推荐阅读