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

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

本文关键字:类型 包含多 std vector 一个 种类      更新时间:2023-10-16

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

如:

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

然后创建了一个vector来存储基类"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

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

为什么代码不工作:

上调用虚函数不使用多态性。它调用的函数是根据编译器看到的符号类型定义的,而不是运行时类型。当您将子类型插入基类型的向量中时,您的值将被转换为基类型("类型切片"),这不是您想要的。在它们上调用函数现在将调用为基类型定义的函数,因为没有是该类型的

如何解决这个问题?

同样的问题可以用下面的代码片段重现:
templateInterface x = templateClass<int>(); // Type slicing takes place!
x.someFunction();  // -> templateInterface::someFunction() is called!

多态性只作用于指针引用类型。然后,它将使用指针/引用后面的对象的运行时类型来决定调用哪个实现(通过使用它的虚函数表)。

转换指针对于类型切片来说是完全"安全"的。您的实际值根本不会被转换,多态性将按预期工作。

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

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.

向量呢?

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

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

确保所有函数都是虚的,可以被子类覆盖。否则将不会调用它们的覆盖版本。但是既然你已经介绍了一个"接口",我相信你是在处理抽象函数。

替代方法:

另一种方法是在vector中使用变体类型。有一些变体类型的实现,比如Boost。变体是一个很流行的词。如果您没有类型层次结构(例如,当您存储基本类型时),这种方法特别好。然后,您将使用像std::vector<boost::variant<int, char, bool>>

这样的向量类型。你会需要非模板基础。除此之外,你需要做出决定容器中实际对象所在的位置。如果他们都是静态对象(具有足够的生命周期),只是使用a std::vector<TemplateInterface*>,并插入v.push_back(&t1);等,应该可以达到这个效果。否则,您可能希望支持克隆,并将克隆保存在向量:最好与Boost指针容器,但std::shared_ptr也可以使用。

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

解决方案1

这个解决方案的灵感来自Sean Parent的c++调味演讲。我强烈建议大家去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();
}
结论

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

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

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