【问题标题】:C++ One std::vector containing template class of multiple typesC++ 一个包含多种类型模板类的 std::vector
【发布时间】:2013-05-07 19:39:00
【问题描述】:

我需要在一个向量中存储多种类型的模板类。

例如,对于:

template <typename T>
class templateClass{
     bool someFunction();
};

我需要一个向量来存储所有:

templateClass<int> t1;
templateClass<char> t2;
templateClass<std::string> t3;
etc

据我所知这是不可能的,如果可以的话,有人能说一下吗?

如果不可能,有人可以解释如何进行以下工作吗?

作为一种变通方法,我尝试使用基础的非模板类并从中继承模板类。

 class templateInterface{
     virtual bool someFunction() = 0;
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction();
 };

然后我创建了一个向量来存储基本的“templateInterface”类:

std::vector<templateInterface> v;
templateClass<int> t;
v.push_back(t);

这产生了以下错误:

error: cannot allocate an object of abstract type 'templateInterface'
note: because the following virtual functions are pure within 'templateInterface'
note: virtual bool templateInterface::someFunction()

为了修复这个错误,我通过提供函数体使 templateInterface 中的函数不是纯虚函数,这是编译的,但是在调用函数时不使用覆盖,而是使用虚函数中的函数体。

例如:

 class templateInterface{
     virtual bool someFunction() {return true;}
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction() {return false;}
 };

 std::vector<templateInterface> v;
 templateClass<int> i;
 v.push_back(i);
 v[0].someFunction(); //This returns true, and does not use the code in the 'templateClass' function body

有没有办法解决这个问题,以便使用被覆盖的函数,或者是否有另一种解决方法可以将多个模板类型存储在一个向量中?

【问题讨论】:

标签: c++ templates stdvector


【解决方案1】:

为什么您的代码不起作用:

上调用虚函数不使用多态性。它调用为编译器看到的这个确切符号的类型定义的函数,而不是运行时类型。当您将子类型插入基本类型的向量时,您的值将转换 为基本类型(“类型切片”),这不是您想要的。在它们上调用函数现在将调用为基本类型定义的函数,因为它不是那种类型的is

如何解决这个问题?

同样的问题可以用这段代码sn-p重现:

templateInterface x = templateClass<int>(); // Type slicing takes place!
x.someFunction();  // -> templateInterface::someFunction() is called!

多态性仅适用于 pointerreference 类型。然后它将使用指针/引用后面对象的运行时类型来决定调用哪个实现(通过使用它的vtable)。

就类型切片而言,转换指针是完全“安全的”。您的实际值根本不会被转换,多态性将按预期工作。

例子,类似于上面的代码sn-p:

templateInterface *x = new templateClass<int>();  // No type slicing takes place
x->someFunction();  // -> templateClass<int>::someFunction() is called!

delete x;  // Don't forget to destroy your objects.

向量呢?

因此,您必须在代码中采用这些更改。您可以简单地将 指针 存储到向量中的实际类型,而不是直接存储值。

使用指针时,您还必须注意删除分配的对象。为此,您可以使用自动删除的智能指针unique_ptr 就是这样一种智能指针类型。只要指针超出范围(“唯一所有权” - 范围是所有者),它就会删除指针。假设您的对象的生命周期绑定到范围,这就是您应该使用的:

std::vector<std::unique_ptr<templateInterface>> v;

templateClass<int> *i = new templateClass<int>();    // create new object
v.push_back(std::unique_ptr<templateInterface>(i));  // put it in the vector

v.emplace_back(new templateClass<int>());   // "direct" alternative

然后,使用以下语法在其中一个元素上调用虚函数:

v[0]->someFunction();

确保您将所有应该可以被子类覆盖的函数虚拟。否则将不会调用其覆盖的版本。但是由于您已经引入了“接口”,我相信您正在使用抽象函数。

替代方法:

做你想做的事情的另一种方法是在向量​​中使用 variant 类型。有一些变体类型的实现,Boost.Variant 是一个非常流行的实现。如果您没有类型层次结构(例如,当您存储原始类型时),这种方法特别好。然后你会使用像std::vector&lt;boost::variant&lt;int, char, bool&gt;&gt;这样的向量类型

【讨论】:

  • 智能指针可能是正确的解决方案,但在他的示例代码中,他没有动态分配对象。如果它们是具有静态生命周期的对象,您不想在它们上使用智能指针,只需获取它们的地址即可。如果他确实需要一个副本(因为初始化对象没有足够的生命周期),那么你可能应该这么说。
  • v.emplace_back(new templateClass&lt;int&gt;();
  • @JamesKanze:在他的原始代码中,向量包含本地人的副本。除非他另有说明,否则我猜该向量需要保持包含副本,这需要动态分配多态性。
  • 并且调用虚函数总是解析为对象的运行时类型。问题不在于缺少动态调度;问题是切片。
  • 感谢两位cmets,我编辑了我的答案并尝试解决这些问题。
【解决方案2】:

多态只能通过指针或引用起作用。你会 需要非模板基础。除此之外,您还需要决定 容器中的实际对象将存在的位置。如果他们都是 静态对象(具有足够的生命周期),只需使用 一个std::vector&lt;TemplateInterface*&gt;,并插入 v.push_back(&amp;t1); 等应该可以解决问题。否则, 您可能希望支持克隆,并将克隆保留在 矢量:最好使用 Boost 指针容器,但是 std::shared_ptr 也可以使用。

【讨论】:

    【解决方案3】:

    到目前为止给出的解决方案都很好,但请注意,如果您在示例中返回 bool 以外的模板类型,这些都无济于事,因为无法事先测量 vtable 插槽。从设计的角度来看,使用面向模板的多态解决方案实际上是有限制的。

    【讨论】:

      【解决方案4】:

      解决方案编号。 1

      这个解决方案的灵感来自 Sean Parent 的 C++ Seasoning talk。我强烈建议您在 youtube 上查看。我的解决方案稍微简化了一点,关键是将对象存储在方法本身中。

      只有一种方法

      创建一个将调用存储对象方法的类。

      struct object {
          template <class T>
          object(T t)
          : someFunction([t = std::move(t)]() { return t.someFunction(); })
          { }
      
          std::function<bool()> someFunction;
      };
      

      那就这样用吧

      std::vector<object> v;
      
      // Add classes that has 'bool someFunction()' method
      v.emplace_back(someClass());
      v.emplace_back(someOtherClass());
      
      // Test our vector
      for (auto& x : v)
          std::cout << x.someFunction() << std::endl;
      

      几种方法

      对于多个方法,使用共享指针在方法之间共享对象

      struct object {
          template <class T>
          object(T&& t) {
              auto ptr = std::make_shared<std::remove_reference_t<T>>(std::forward<T>(t));
              someFunction = [ptr]() { return ptr->someFunction(); };
              someOtherFunction = [ptr](int x) { ptr->someOtherFunction(x); };
          }
      
          std::function<bool()> someFunction;
          std::function<void(int)> someOtherFunction;
      };
      

      其他类型

      原始类型(例如intfloatconst char*)或类(std::string 等)可以以与object 类相同的方式包装,但行为不同。例如:

      struct otherType {
          template <class T>
          otherType(T t)
          : someFunction([t = std::move(t)]() {
                  // Return something different
                  return true;
              })
          { }
      
          std::function<bool()> someFunction;
      };
      

      所以现在可以添加没有someFunction 方法的类型。

      v.emplace_back(otherType(17));      // Adding an int
      v.emplace_back(otherType("test"));  // A string
      

      解决方案编号。 2

      经过一番思考,我们在第一个解决方案中基本上所做的是创建可调用函数数组。那么为什么不直接执行以下操作呢。

      // Example class with method we want to put in array
      struct myclass {
          void draw() const {
              std::cout << "myclass" << std::endl;
          }
      };
      
      // All other type's behaviour
      template <class T>
      void draw(const T& x) {
          std::cout << typeid(T).name() << ": " << x << std::endl;
      }
      
      int main()
      {
          myclass x;
          int y = 17;
      
          std::vector<std::function<void()>> v;
      
          v.emplace_back(std::bind(&myclass::draw, &x));
          v.emplace_back(std::bind(draw<int>, y));
      
          for (auto& fn : v)
              fn();
      }
      

      结论

      解决方案编号。 1 绝对是一个有趣的方法,不需要继承也不需要虚函数。并且可以用于其他需要存储模板参数以供以后使用的东西。

      解决方案编号。另一方面,2 更简单、更灵活,可能是更好的选择。

      【讨论】:

        【解决方案5】:

        如果您正在寻找存储多种类型的容器,那么您应该探索流行的 boost 库中的 boost variant

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-01-02
          • 1970-01-01
          • 1970-01-01
          • 2012-09-29
          相关资源
          最近更新 更多