在下面的代码中,当我试图将fooBaz
推到v
时,我得到一个编译器错误。这让我很惊讶,因为Baz
是Bar
的派生类。
为什么这是不允许的,我能做些什么,如果我想把几个Foo
实例,模板化的类派生自相同的基类,成一个矢量?
#include <iostream>
#include <vector>
template<typename T>
class Foo {};
struct Bar {};
struct Baz : public Bar {};
int main() {
Foo<Bar> fooBar;
Foo<Baz> fooBaz;
std::vector<Foo<Bar>> v;
v.push_back(fooBar);
v.push_back(fooBaz);
return 0;
}
Java泛型与c++模板不同。
c++的类类型值与Java的类类型引用变量是不同的。
这两个问题你都碰到了。
c++模板为每组模板参数生成一个新的、不相关的类型。你可以创建一个公共基础,但你必须自己做。
Java泛型实际上创建了一个类。然后在输入和输出处写强制转换操作。
所以Java泛型,Foo<Base>
和Foo<Derived>
是相关的,因为Java泛型实际上创建了Foo<Object>
,然后将其包装在强制转换中,并且Foo<Base>
和Foo<Derived>
中的强制转换是兼容的。(好吧,不总是Object
,你用信息标记泛型参数,Java使用这些信息来确定它写泛型的实际类型,但这给了你一个想法)。
在c++中,没有关系。(模板模式匹配提供了编译时关系,但根本没有运行时关系)
第二个问题是,您将类类型的值视为引用。在c++中,Foo
是一个实际的foo。它表示一个内存块,该内存块是该类的一个实例。在Java中,Foo
是一个智能指针,指向堆上某处遵循Foo
协议的对象(是一个派生类)。
在Java中不能很容易地创建Foo
类型的值,在c++中也不能很容易地创建标记并将智能指针扫描到Foo
类型。
Foo<Bar> fooBar;
Foo<Baz> fooBaz;
这是两个不相关的类型。它们被存储在堆栈中(自动存储)。
std::vector<Foo<Bar>> v;
存储一个包含Foo<Bar>
对象的内存缓冲区。
v.push_back(fooBar);
将fooBar
实例从自动存储复制到vector
。
v.push_back(fooBaz);
这不起作用,因为fooBar
和fooBaz
是不相关的类型。
现在,在c++23反射之前,在c++中模仿Java的工作是很困难的。你必须手动执行一些步骤。
首先,当手动告诉Foo
理解继承:
struct empty_t {};
template<class T, class Base=empty_t>
class Foo:Foo<Base> {};
template<>
class Foo<empty_t, empty_t> {
virtual ~Foo() {}
};
struct Bar {};
struct Baz : public Bar {};
auto fooBar = std::make_unique<Foo<Bar>>();
auto fooBaz = std::make_unique<Foo<Baz, Bar>>();
std::vector<std::unique_ptr<Foo<Bar>>> v;
v.push_back(std::move(fooBar));
v.push_back(std::move(fooBaz));
这个编译。
在c++23中,编译时反射可以让你自动检测Baz
的基类,如果你愿意,可以让Foo<Baz>
自动从Foo<Bases>...
继承。
现在,继承只是c++中处理多态性的一种方法,但我认为今天已经足够了。
正如评论中提到的,模板就像食谱。在类模板成为模板之前,需要实例化类模板来获得类。不同的实例化之间没有隐含的关系(除了相同模板的实例化)。当您考虑以下示例时,可能会更清楚:
template<typename T> struct Foo {};
template <> struct Foo<int> { void bar(){} };
template <> struct Foo<double> { void moo(){} };
int main() {
Foo<int> x;
x.bar();
Foo<double> y;
y.moo();
}
Foo<int>
和Foo<double>
是两个不相关的类型,方法完全不同。如果它们不是模板的实例化,而是"普通的";如果不能将FooA
压入std::vector<FooB>
中,那就不足为奇了。
std::vector<Foo<Bar>>
只能保存Foo<Bar>
类型的元素。vector不知道这个类型是实例化模板Foo
的结果。即使是这样,你也不能把Foo<Bar>
赋值给Foo<Baz>
,除非你提供一个转换。
实际上你的误解还有更多需要被揭穿的地方。假设你有一个std::vector<Bar>
那么你也不能将Baz
压入该向量。看这里为什么不:什么是对象切片?这里是如何做的:https://stackoverflow.com/a/16126649/4117728.