【问题标题】:Adding instances of a template class to a vector (C++)将模板类的实例添加到向量 (C++)
【发布时间】:2021-02-26 17:05:27
【问题描述】:

在下面的代码中,我在尝试将 fooBaz 推送到 v 时遇到编译器错误。这让我感到惊讶,因为BazBar 的派生类。

为什么不允许这样做,如果想将多个 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;
}

【问题讨论】:

  • “这让我很惊讶,因为 Baz 是 Bar 的派生类” - 但是 Foo&lt;Baz&gt; 不是 Foo&lt;Bar&gt; 的派生类。
  • Foo&lt;Bar&gt;Foo&lt;Baz&gt; 是完全不相关的类,为什么你会期望std::vector&lt;T&gt; 可以包含混合类型,除非这些是指向virtually 继承类的指针或引用
  • 打个冷却类比:类模板是一个食谱,其中模板参数是成分。换了食材,就换了菜,不希望在同一个碗里放不同的菜。
  • 哈哈,好比喻内森。谢谢大家的回答,很有帮助。
  • Java 程序员?

标签: c++ templates generics polymorphism


【解决方案1】:

Java 泛型与 C++ 模板不同。

类类型的 C++ 值与类类型的 Java 引用变量不同。

你在这里遇到了这两个问题。

C++ 模板为每组模板参数生成一个新的、不相关的类型。您可以创建一个公共基础,但您必须自己动手。

Java 泛型实际上创建了一个类。然后它在输入和输出上编写转换操作。

所以一个 Java 泛型,Foo&lt;Base&gt;Foo&lt;Derived&gt; 是相关的,因为 Java 泛型实际上创建了一个 Foo&lt;Object&gt; 然后将其包装在强制转换中,Foo&lt;Base&gt;Foo&lt;Derived&gt; 中的强制转换是兼容的。 (嗯,并非总是Object,您使用 Java 用来确定其为其编写 Generic 的实际类型的信息标记泛型参数,但这为您提供了思路)。

在 C++ 中,没有关系。 (嗯,模板模式匹配给你一个编译时关系,但根本没有运行时关系)

第二个问题是您将类类型的值视为引用。在 C++ 中,Foo 是一个实际的 foo。它表示一块内存,它是该类的一个实例。在 Java 中,Foo 是一个智能指针,指向堆上某个遵循Foo 协议(是派生类)的对象。

在 Java 中不能轻易地生成 Foo 类型的值,在 C++ 中也不能轻易地标记和清除指向 Foo 的智能指针。

Foo<Bar> fooBar;
Foo<Baz> fooBaz;

这是两种不相关的类型。它们存储在堆栈中(自动存储)。

std::vector<Foo<Bar>> v;

这存储了一个内存缓冲区,其中包含打包在一起的Foo&lt;Bar&gt; 对象。

v.push_back(fooBar);

这会将fooBar 实例从自动存储复制到vector

v.push_back(fooBaz);

这不起作用,因为fooBarfooBaz 是不相关的类型。

现在,在 反射之前,在 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));

这会编译。

编译时反射应该让您自动检测Baz 的基类,并让Foo&lt;Baz&gt; 自动从Foo&lt;Bases&gt;... 继承。

现在,继承只是 C++ 中处理多态性的一种方式,但我认为对于今天来说已经足够了。

【讨论】:

    【解决方案2】:

    正如评论中提到的,模板就像食谱。在它只是一个模板之前,您需要实例化一个类模板来获取一个类。不同的实例化之间没有隐含的关系(除了同一个模板的实例化)。当您考虑以下示例时,这可能会变得更加清楚:

    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&lt;int&gt;Foo&lt;double&gt; 是两种完全不同的方法的不相关类型。如果它们不是模板的实例化,而是具有不同成员的“普通”类型,您不会对无法将 FooA 推送到 std::vector&lt;FooB&gt; 感到惊讶。

    std::vector&lt;Foo&lt;Bar&gt;&gt; 只能容纳 Foo&lt;Bar&gt; 类型的元素。向量不知道这种类型是实例化模板Foo 的结果。即使是这样,除非您提供转换,否则您不能将 Foo&lt;Bar&gt; 分配给 Foo&lt;Baz&gt;


    实际上,您的误解还有很多需要揭穿。假设您有一个std::vector&lt;Bar&gt;,那么您也不能Baz 推送到该向量。为什么不看这里:What is object slicing?。而这里是做什么:https://stackoverflow.com/a/16126649/4117728

    【讨论】:

    • 谢谢。这很有帮助。
    猜你喜欢
    • 2014-01-25
    • 1970-01-01
    • 1970-01-01
    • 2022-06-27
    • 1970-01-01
    • 2021-09-23
    • 2021-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多