派生类的模板类作为函数的参数 - 危险?
Posted
技术标签:
【中文标题】派生类的模板类作为函数的参数 - 危险?【英文标题】:template class of derived class as parameter to function - dangers? 【发布时间】:2019-08-29 00:02:25 【问题描述】:编辑:澄清一下,我理解为什么这段代码不起作用,我并不是要修复它,而是要理解如果这段代码可以在没有语义错误的情况下编译会有什么危险。强>
我发现以下代码会导致静态语义错误。
我知道这是因为 std::list 是一个模板类,这意味着 std::list<foo*>
与 std::list<bar*>
是不同的类型
我的问题是,如果编译器允许在第二次调用 print_all 时从 std::list<bar*>
到 std::list<foo*>
进行这种类型的转换,会有什么危险?
我在该网站上进行了搜索,但找不到可能发生的坏事的示例。 我也尝试过思考这样的例子,但我不确定这些例子是否正确或准确。 (例如,如果编译器允许这样做,我是否可以将 foo* 对象添加到 print_all() 中的 bar 列表中,因为它已转换为列表?)。
#include <list>
using std::list;
class foo
// ...
;
class bar : public foo
// ...
;
static void print_all(list<foo*>& L)
// ...
list<foo*> LF;
list<bar*> LB;
// ...
print_all(LF); // works fine
print_all(LB); // static semantic error
【问题讨论】:
【参考方案1】:list<foo*>
和list<bar*>
是两个完全不同的类,它们之间没有任何联系,除了从同一个模板std::list
(template<class T, class Allocator = std::allocator<T>> class list;
) 生成的蜜蜂。因此,它们之间没有转换,除非模板std::list
的作者在T
是U
的基类时明确编写了std::list<T, A>
和std::list<U, A>
之间的转换。那不是写的。
实现您想要的一种方法是创建模板函数:
template <class T, class A>
void print_all(std::list<T*, A>& l)
现在,有一些注意事项需要注意:
我看不出你为什么要制作那个静态成员。我会让它成为免费功能,但是我会把它放在一个带有foo
和bar
的命名空间中
如果您想将其使用严格限制为foo
及其派生类,您可以使用sfinae:
template <class T, class A, class E = std::enable_if_t<std::is_base_of_v<foo, T>>>
auto print_all(std::list<T*, A>& l)
最后,您应该考虑将其转换为惯用的 C++ 打印方式,也就是流,并添加 operator<<(std::ostream&, const std::list<T*, A>&)
和 operator<<(std::ostream, const foo&)
以及可能在 foo
上的虚拟打印功能。将它们定义在与 foo
和 bar
相同的命名空间中尤为重要。
【讨论】:
首先感谢您的详细回答,但是如果此代码(没有您建议的更改)可以编译,我没有找到答案可能会发生什么坏事? (如果编译器允许第二次调用 print_all)。 @Daniel 正如我所说,这不取决于编译器。这两个类是不同的类,除了从同一个模板生成之外,它们之间没有任何关系。 @Daniel :foo
和 bar
之间有继承关系。 std::list<foo>
和 std::list<bar>
之间没有任何关系。
是的,我知道这一点,我是说如果允许这种行为可能会导致什么危险,我很感兴趣。
例如,此代码也不适用于 java 泛型,即使它是不同的机制。【参考方案2】:
您不能使用一个向量代替另一个向量,因为它们是不同的类,但您可以使用转换或复制/移动构造函数和赋值运算符将一个向量隐式或显式转换为另一个向量。它可能会花费您额外的性能(以及复制时的内存)开销,但在您的特定情况下可能是一笔交易。
【讨论】:
以上是关于派生类的模板类作为函数的参数 - 危险?的主要内容,如果未能解决你的问题,请参考以下文章