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

时间:2013-05-13 17:23:17

标签: c++ templates stdvector

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

例如,对于:

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中的函数不是一个纯虚函数,这个函数体已经编译但是在调用函数时不使用overide,而是使用虚函数中的body。

例如:

 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

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

5 个答案:

答案 0 :(得分:26)

为什么您的代码不起作用

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

如何解决此问题?

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

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

确保您创建所有 virtual 函数,这些函数应该可以被子类覆盖。否则,将不会调用它们被覆盖的版本。但是既然你已经引入了一个“界面”,我相信你正在使用抽象函数。

替代方法:

执行所需操作的其他方法是在向量​​中使用变量类型。有一些变体类型的实现,Boost.Variant是一个非常受欢迎的类型。如果您没有类型层次结构(例如,存储基元类型时),此方法尤其好用。然后,您将使用类似std::vector<boost::variant<int, char, bool>>

的矢量类型

答案 1 :(得分:2)

多态性只能通过指针或引用来实现。你会 需要非模板库。除此之外,你需要做出决定 容器中的实际对象将存在于何处。如果他们都是 静态对象(具有足够的生命周期),只需使用 一个std::vector<TemplateInterface*>,并插入 v.push_back(&t1);等应该可以解决问题。除此以外, 你可能想要支持克隆,并保持克隆 vector:最好用Boost指针容器,但是 std::shared_ptr也可以使用。

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

解决方案。 1

此解决方案的灵感来自于Sean Parent的C ++ Seasoning演讲。我强烈建议您在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更简单,更灵活,可能是一个更好的选择。