C++ 一个包含多种类型模板类的 std::vector

Posted

技术标签:

【中文标题】C++ 一个包含多种类型模板类的 std::vector【英文标题】:C++ One std::vector containing template class of multiple types 【发布时间】: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

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

【问题讨论】:

看这个链接***.com/questions/5627215/… 【参考方案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。

【讨论】:

以上是关于C++ 一个包含多种类型模板类的 std::vector的主要内容,如果未能解决你的问题,请参考以下文章

预期的枚举`std::result::Result`,发现结构`std::vec::Vec`。

C++ 派生模板类:访问实例的受保护成员

C++:模板实现(代码风格)

在 C++ 中使用静态变量 [重复]

c++ 使用友好类的类型作为模板参数声明 stl 向量

模板类的 C++ 强制转换