C++模板:一个按类列出的列表,如何分解代码

C++ Template : one list by class, how to factorize the code?

本文关键字:列表 何分解 代码 分解 模板 一个 C++      更新时间:2023-10-16

假设我有这个类:

class Component1;
class Component2;
// many different Components
class Component42;
class MyClass
{
public:
    MyClass(void) {};
    std::list<Component1> component1List;
    std::list<Component2> component2List;
    // one list by component
    std::list<Component42> component42List;
};

我想创建一个具有以下签名的函数:

template<class T> void addElement(T component);

它应该执行以下操作:

  • 如果component的类型为Component1,则将其添加到Component1List
  • 如果component的类型为Component2,则将其添加到Component2List,等等

有可能吗?做这件事的好方法是什么?

我可以通过以下函数获得相同的行为:

template<class T> void addElement(int componentType, T component);

但我不想这样指定componentType:这是无用的信息,它为可能的错误打开了大门(如果componentType不代表组件的类型)。

std::tuple前往救援。

变更日志:

  • 使用std::decay_t

  • 添加了的可变参数

  • add_component()现在返回对此的引用以允许调用链接。


#include <iostream>
#include <list>
#include <utility>
#include <type_traits>
#include <tuple>
class Component1 {};
class Component2 {};
struct Component3 {
    Component3() {}
};
// many different Components
template<class...ComponentTypes>
class MyClassImpl
{
    template<class Component> using list_of = std::list<Component>;
public:
    using all_lists_type =
    std::tuple<
    list_of<ComponentTypes> ...
    >;

    // add a single component
    template<class Component>
    MyClassImpl& add_component(Component&& c)
    {
        list_for<Component>().push_back(std::forward<Component>(c));
        return *this;
    }
    // add any number of components
    template<class...Components>
    MyClassImpl& add_components(Components&&... c)
    {
        using expand = int[];
        void(expand { 0, (void(add_component(std::forward<Components>(c))), 0)... });
        return *this;
    }

    template<class Component>
    auto& list_for()
    {
        using component_type = std::decay_t<Component>;
        return std::get<list_of<component_type>>(_lists);
    }
    template<class Component>
    const auto& list_for() const
    {
        using component_type = std::decay_t<Component>;
        return std::get<list_of<component_type>>(_lists);
    }

private:
    all_lists_type _lists;
};
using MyClass = MyClassImpl<Component1, Component2, Component3>;
int main()
{
    MyClass c;
    c.add_component(Component1());
    c.add_component(Component2());
    const Component3 c3;
    c.add_component(c3);
    c.add_components(Component1(),
                     Component2(),
                     Component3()).add_components(Component3()).add_components(Component1(),
                                                                               Component2());
    std::cout << c.list_for<Component1>().size() << std::endl;
    return 0;
}

最直接的变体是不使用模板,而是重载addElement()函数:

void addElement(Component1 element)
{
    this->element1List.push_back(element);
}
void addElement(Component2 element)
{
    this->element2List.push_back(element);
}
// ... etc

然而,如果你有很多这样的东西(我想你不仅仅有addElement()),这可能会变得乏味。使用宏为每种类型生成代码仍然可以在合理的努力下完成这项工作。

如果你真的想使用模板,你可以使用一个模板函数,并为每种类型专门化模板函数。尽管如此,与上述方法相比,这并没有减少代码重复的数量。此外,您仍然可以使用宏生成代码来减少它。

然而,有希望以一种通用的方式来做到这一点。首先,让我们创建一个保存列表的类型:

template<typename T>
struct ComponentContainer
{
    list<T> componentList;
};

现在,派生类只是继承自这个类,并使用C++类型的系统来定位正确的容器基类:

class MyClass:
    ComponentContainer<Component1>,
    ComponentContainer<Component2>,
    ComponentContainer<Component3>
{
public:
    template<typename T>
    void addElement(T value)
    {
        ComponentContainer<T>& container = *this;
        container.componentList.push_back(value);
    }
}

此处注释:

  • 这使用了私有继承,这与您最初使用的包含非常相似
  • 尽管ComponentContainer是一个基类,但它没有任何虚拟函数,甚至没有虚拟析构函数。是的,这很危险,应该清楚地记录下来。不过,我不会添加虚拟析构函数,因为它有开销,而且不应该需要它
  • 您可以完全放弃中间容器,也可以从list<T>派生。我没有这样做,因为它将使list的所有成员函数在类MyClass中可用(即使不是公开的),这可能会令人困惑
  • 不能将addElement()函数放入基类模板中以避免派生类中的模板。简单的原因是为了addElement()函数而扫描不同的基类,然后才执行过载解析。因此,编译器将只在第一个基类中找到addElement()
  • 这是一个简单的C++98解决方案,对于C++11,我会看看Jens和Richard提出的基于类型的元组查找解决方案

如果没有太多的类,可以使用重载。基于模板的解决方案可以通过基于类型的元组查找来完成:

class MyClass {
public:
    template<typename T> void addElement(T&& x) {
         auto& l = std::get<std::list<T>>(lists);
         l.insert( std::forward<T>(x) );
    }        
private:
    std::tuple< std::list<Component1>, std::list<Component2> > lists;
};

如果您在实例化多容器时事先不知道需要存储的类型,则可以选择隐藏类型并使用type_index保存列表映射:

struct Container {
    struct Entry {
        void *list;
        std::function<void *(void*)> copier;
        std::function<void(void *)> deleter;
    };
    std::map<std::type_index, Entry> entries;
    template<typename T>
    std::list<T>& list() {
        Entry& e = entries[std::type_index(typeid(T))];
        if (!e.list) {
            e.list = new std::list<T>;
            e.deleter = [](void *list){ delete ((std::list<T> *)list); };
            e.copier = [](void *list){ return new std::list<T>(*((std::list<T> *)list)); };
        }
        return *((std::list<T> *)e.list);
    }
    ~Container() {
        for (auto& i : entries) i.second.deleter(i.second.list);
    }
    Container(const Container& other) {
        // Not exception safe... se note
        for (auto& i : other.entries) {
            entries[i.first] = { i.second.copier(i.second.list),
                                 i.second.copier,
                                 i.second.deleter };
        }
    };
    void swap(Container& other) { std::swap(entries, other.entries); }
    Container& operator=(const Container& other) {
        Container(other).swap(*this);
        return *this;
    };
    Container() { }
};

可以用作:

Container c;
c.list<int>().push_back(10);
c.list<int>().push_back(20);
c.list<double>().push_back(3.14);

注意:现在编写的复制构造函数不是异常安全的,因为如果copier抛出(由于内存不足或列表中元素的复制构造函数抛出),则不会释放已分配的列表。

void addElement(Component1 component) {
   componentList1.insert(component);
}
void addElement(Component2 component) {
   componentList2.insert(component);
}