56

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:

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.

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?

4

5 回答 5

34

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

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

如何解决这个问题?

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

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

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

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

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

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

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

替代方法:

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

于 2013-05-13T17:26:17.167 回答
3

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

于 2013-05-13T17:29:00.747 回答
2

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

于 2015-07-06T18:23:03.057 回答
2

解决方案编号。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;
};

其他类型

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

所以现在可以添加没有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 更简单、更灵活,可能是这里更好的选择。

于 2018-09-17T12:18:26.443 回答
1

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

于 2013-05-13T17:33:24.620 回答