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!
多态性仅适用于 pointer 或 reference 类型。然后它将使用指针/引用后面对象的运行时类型来决定调用哪个实现(通过使用它的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<boost::variant<int, char, bool>>
这样的向量类型
【讨论】:
智能指针可能是正确的解决方案,但在他的示例代码中,他没有动态分配对象。如果它们是具有静态生命周期的对象,您不想在它们上使用智能指针,只需获取它们的地址即可。如果他确实需要一个副本(因为初始化对象没有足够的生命周期),那么你可能应该这么说。v.emplace_back(new templateClass<int>();
@JamesKanze:在他的原始代码中,向量包含本地人的副本。除非他另有说明,否则我猜该向量需要保持包含副本,这需要动态分配多态性。
并且调用虚函数总是解析为对象的运行时类型。问题不在于缺少动态调度;问题是切片。
感谢两位cmets,我编辑了我的答案并尝试解决这些问题。【参考方案2】:
多态只能通过指针或引用起作用。你会
需要非模板基础。除此之外,您还需要决定
容器中的实际对象将存在的位置。如果他们都是
静态对象(具有足够的生命周期),只需使用
一个std::vector<TemplateInterface*>
,并插入
v.push_back(&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;
;
其他类型
原始类型(例如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 更简单、更灵活,可能是更好的选择。
【讨论】:
【参考方案5】:如果您正在寻找存储多种类型的容器,那么您应该探索流行的 boost 库中的 boost variant。
【讨论】:
以上是关于C++ 一个包含多种类型模板类的 std::vector的主要内容,如果未能解决你的问题,请参考以下文章