【问题标题】:Container of template classes without template parameter没有模板参数的模板类的容器
【发布时间】:2016-11-07 21:24:04
【问题描述】:

我想知道您是否可以拥有一个包含具有不同模板参数的对象的容器。

我正在尝试实现这样的目标:

#include <iostream>
#include <list>

template <class T>
class base
{
    public:
        T val;
        base(T newVal): val(newVal) {}; 
};

class derived : public base<int>
{
    public:
        derived(int newVal): base(newVal) {}; 
};

int main ( void )
{
    std::list < base<?> > base_collection;
    return 0;
}

我希望我当前的项目尽可能灵活和动态,当需要新的派生类时几乎不需要额外的编码,而我当前的实现使得存在这样的列表很重要。

是否有一种常用的、有益的、干净的方式来实现这一目标?

【问题讨论】:

  • “我当前的实现使得存在这样一个 [heterogeneous] 列表很重要”。哦。这是一种反模式。你需要放弃那种设计。
  • 不,你不能。 base&lt;int&gt;base&lt;float&gt;完全不同的类型。而且你不能将派生类对象存储在基类变量中。

标签: c++ class c++11 polymorphism containers


【解决方案1】:

目前尚不完全清楚您为什么需要这样做,或者您打算对列表的元素执行哪些操作(顺便说一句,请考虑改用 std 向量)。我建议您创建一个通用的非模板基类,该基类继承自:

struct mainbase {
  virtual ~mainbase() = default;
};

template <class T>
class base : public mainbase
{
    public:
        T val;
        base(T newVal): val(newVal) {}; 
};


class derived : public base<int>
{
    public:
        derived(int newVal): base(newVal) {}; 
};

int main ( void )
{
    std::list < std::unique_ptr<mainbase>> > base_collection;
    return 0;
}

毕竟,如果您要将它们全部放在一个向量中,您很可能需要一组可以对这些对象执行的通用操作。把它们放在mainbase

正如@BenjaminLindley 指出的那样,您不能按值进行多态性。这就是为什么你会使用指针(such as unique_ptr):std::unique_ptr&lt;mainbase&gt;

对于 C++17,有一个针对 std::any 的提案(正在进行中),可以使用它来代替,但您仍然需要执行特定的强制转换才能获得具有正确类型的内容。

【讨论】:

  • 如何在不强制转换的情况下从 mainbase 对象访问 val?
  • @reign,你真的不能。
  • @JohanLundberg:如果您只是存储 mainbase 对象,则不可能有多态行为。您无法使用派生类功能。
  • @BenjaminLindley,谢谢 - 对,刚刚复制了原始代码。列表。我会更新的。
  • @reign:你打算用 val 做什么?如果 val 可以是任何类型,你怎么知道哪些操作是可能的?
【解决方案2】:

一个可能的实现是使用双重调度

#include <iostream>
#include <list>

struct visitor;

struct dispatchable {
    virtual void accept(visitor &v) = 0;
};

template <class>
struct base;

struct visitor {
    template<typename T>
    void visit(base<T> &);
};

template <class T>
struct base: dispatchable {
    T val;
    base(T newVal): val(newVal) {};
    void accept(visitor &v) override { v.visit(*this); }
};

struct derivedInt : base<int> {
    derivedInt(int newVal): base(newVal) {}; 
};

struct derivedDouble : base<double> {
    derivedDouble(double newVal): base(newVal) {}; 
};

template<>
void visitor::visit(base<int> &) {
    std::cout << "int" << std::endl;
}

template<>
void visitor::visit(base<double> &) {
    std::cout << "double" << std::endl;
}

int main ( void ) {
    visitor v{};
    std::list <dispatchable*> coll;
    coll.push_back(new derivedInt{42});
    coll.push_back(new derivedDouble{.42});
    for(auto d: coll) d->accept(v);
}

这样,您只需定义处理您要引入的新base&lt;T&gt; 类型的专用函数。
例如,如果你想使用base&lt;char&gt;,你必须定义:

template<>
void visitor::visit(base<char> &) {
    std::cout << "char" << std::endl;
}

请注意,我假设您想以不同的方式处理 base&lt;T&gt; 的每个专业化。否则,定义泛型成员函数 visitor::visit 并删除特化就足够了。


旁注:不要使用裸指针。
这是一个例子。在生产代码中,我会改用智能指针。

【讨论】:

  • 这是可以接受的,只要每个类只有一个相关操作,例如process event
  • @JohanLundberg OP 想要为每个 T 处理 base&lt;T&gt;,仅此而已。正如他在对您的回答的评论中提到的那样,他想要使用val。这样他就可以使用了。双重调度非常适合这种情况。究竟是什么问题?
  • @skypjack,“问题”,如果您想为每种类型做除 cout 之外的其他事情时,这就是您的负担。你的回答很好。
猜你喜欢
  • 2013-05-11
  • 1970-01-01
  • 2011-08-25
  • 2011-10-25
  • 2022-01-09
  • 2018-06-25
  • 1970-01-01
  • 2018-11-05
相关资源
最近更新 更多