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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16527673/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:27:08  来源:igfitidea点击:

C++ One std::vector containing template class of multiple types

c++templatesstdvector

提问by jtedit

I need to store multiple types of a template class in a single vector.

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

Eg, for:

例如,对于:

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

I need one vector that will store all of:

我需要一个可以存储所有内容的向量:

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

As far as I know this is not possible, if it is could someone say how?

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

If it isn't possible could someone explain how to make the following work?

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

As a work around I tried to use a base, non template class and inherit the template class from it.

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

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

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

I then created a vector to store the base "templateInterface" class:

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

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

This produced the following error:

这产生了以下错误:

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

To fix this error I made the function in templateInterface not a pure virtual by providing a function body, this compiled but when calling the function the overide is not used, but instead the body in the virtual function.

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

Eg:

例如:

 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

Is there any way to fix this so that the overridden function is used, or is there another workaround to store multiple template types in a single vector?

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

采纳答案by leemes

Why your code doesn't work:

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

Calling a virtual function on a valuedoesn't use polymorphism. It calls the function which is defined for the type of this exact symbol as seen by the compiler, not the runtime type. When you insert sub types into a vector of the base type, your values will be convertedinto the base type ("type slicing"), which is not what you want. Calling functions on them will now call the function as defined for the base type, since not it isof that type.

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

How to fix this?

如何解决这个问题?

The same problem can be reproduced with this code snippet:

使用此代码片段可以重现相同的问题:

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

Polymorphism only works on a pointeror referencetype. It will then use the runtime typeof the object behind the pointer / reference to decide which implementation to call (by using it's vtable).

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

Converting pointers is totally "safe" with regard to type slicing. Your actualvalues won't be converted at all and polymorphism will work as expected.

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

Example, analogous to the code snippet above:

示例,类似于上面的代码片段:

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.

What about vectors?

向量呢?

So you have to adopt these changes in your code. You can simply store pointersto actual types in the vector, instead of storing the values directly.

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

When working with pointers you also have to care about deleting your allocated objects. For this you can use smart pointerswhich care about deletion automatically. unique_ptris one such smart pointer type. It deletes the pointee whenever it goes out of scope ("unique ownership" - the scope being the owner). Assuming the lifetime of your objects is bound to the scope this is what you should use:

使用指针时,您还必须关心删除已分配的对象。为此,您可以使用自动关心删除的智能指针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

Then, call a virtual function on one of these elements with the following syntax:

然后,使用以下语法对这些元素之一调用虚拟函数:

v[0]->someFunction();

Make sure you make all functions virtualwhich should be possible to be overridden by subclasses. Otherwise their overridden version will not be called. But since you already introduced an "interface", I'm sure you are working with abstract functions.

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

Alternative approaches:

替代方法:

Alternative ways to do what you want is to use a varianttype in the vector. There are some implementations of variant types, the Boost.Variantbeing a very popular one. This approach is especially nice if you don't have a type hierarchy (for example when you store primitive types). You would then use a vector type like std::vector<boost::variant<int, char, bool>>

执行您想要的操作的替代方法是在向量中使用变体类型。有一些变体类型的实现,Boost.Variant是一种非常流行的实现。如果您没有类型层次结构(例如,当您存储原始类型时),这种方法特别好。然后你会使用一个向量类型std::vector<boost::variant<int, char, bool>>

回答by James Kanze

Polymorphism only works through pointers or references. You'll need the non-template base. Beyond that, you'll need to decide where the actual objects in container will live. If they're all static objects (with sufficient lifetime), just using a std::vector<TemplateInterface*>, and inserting with v.push_back(&t1);, etc., should do the trick. Otherwise, you'll probably want to support cloning, and keep clones in the vector: preferably with Boost pointer containers, but std::shared_ptrcan be used as well.

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

回答by Mário de Sá Vera

The solutions given so far are fine though be aware that in case you were returning the template type other than bool in your example , none of these would help as the vtable slots would not be able to be measured before hand. There are actually limits , from a design point of view , for using a template oriented polymorphic solution.

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

回答by Vladimir Talybin

Solution nr. 1

解决方案编号 1

This solution inspired by Sean Parent's C++ Seasoning talk. I highly recommend to check it out on youtube. My solution simplified a bit and the key is to store object in method itself.

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

One method only

只有一种方法

Create a class that will invoke method of stored object.

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

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

    std::function<bool()> someFunction;
};

Then use it like this

然后像这样使用它

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;

Several methods

几种方法

For several methods use shared pointer to share object between methods

对于几种方法使用共享指针在方法之间共享对象

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;
};

Other types

其他类型

Primitive types (such as int, float, const char*) or classes (std::stringetc.) may be wrapped in the same way as objectclass do but behave differently. For example:

原始类型(例如int, float, const 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;
};

So now it is possible to add types that does not have someFunctionmethod.

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

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

Solution nr. 2

解决方案编号 2

After some thoughts what we basically done in first solution is created array of callable functions. So why not just do the following instead.

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

// 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();
}

Conclusion

结论

Solution nr. 1 is definitely an interesting method that does not require inheritance nor virtual functions. And can be used to other stuff where you need to store a template argument to be used later.

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

Solution nr. 2, on the other hand, is simpler, more flexible and probably a better choice here.

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

回答by jbo5112

If you're looking at a container to store multiple types, then you should explore boost variantfrom the popular boost library.

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