没有模板参数的模板类容器

3

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

我正在尝试实现类似于这样的东西:

#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;
}

我希望我的当前项目尽可能灵活和动态,当需要一个新的派生类时,不需要太多额外的编码工作,而我的当前实现使得这样的列表存在非常重要。

有没有一种常用、有益且干净的方法可以完全实现这一点?


我的当前实现使得这样一个[异构]列表的存在非常重要。哦,那是一种反模式。你需要放弃那个设计。 - Cheers and hth. - Alf
不行。base<int>base<float>完全不同的类型。而且你无论如何都不能将派生类对象存储在基类变量中。 - user253751
2个回答

3

目前还不清楚您为什么需要这样做,或者您打算对列表元素执行哪些操作(顺便说一下,考虑使用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所指出的,你不能通过值实现多态性。这就是为什么你会使用指针(例如unique_ptr)std::unique_ptr<mainbase>
使用C++17提案(正在进行中)std::any,可以代替unique_ptr,但你仍然需要执行特定的转换才能获得正确类型的内容。

你如何在不进行强制类型转换的情况下,访问 mainbase 对象中的 val 变量? - reign
@reign,你真的不行。 - Johan Lundberg
1
@JohanLundberg:如果你只是存储主要对象,那么你不可能拥有多态行为。你没有办法利用派生类的功能。 - Benjamin Lindley
如果这个base_collection持有指针,那么访问val是可能的吗? - reign
1
@reign:你打算对val做什么?如果val可以是任何类型,你怎么可能知道哪些操作是可能的呢? - Benjamin Lindley
显示剩余2条评论

3
一种可能的实现方法是使用“双重分派”技术:
#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<T>类型的函数。

例如,如果你想使用base<char>,你需要定义:

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

请注意,我假设您希望以不同的方式处理base<T>的每个专业化。否则,定义通用成员函数visitor::visit并删除专业化即可。
副笔:不要使用裸指针。
这只是一个示例。 在生产代码中,我会改用智能指针。

只要每个类只有一个相关操作,例如“处理事件”,这就是可以接受的。 - Johan Lundberg
@JohanLundberg OP想要处理每个T的base<T>,仅此而已。正如他在对您的回答的评论中提到的那样,他想要使用val。这样他就可以使用它了。双重分派在这种情况下很适合。问题到底是什么? - skypjack
@skypjack,如果你愿意这么说的话,“问题”在于当你想要除了cout之外做其他事情时的负担。你的回答很好。 - Johan Lundberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接