C++ 模板:按类列出,如何分解代码?

Posted

技术标签:

【中文标题】C++ 模板:按类列出,如何分解代码?【英文标题】:C++ Template : one list by class, how to factorize the code? 【发布时间】:2016-01-04 08:33:34 【问题描述】:

假设我有这个课程:

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

它应该执行以下操作:

如果componentComponent1 类型,将其添加到Component1List 如果componentComponent2 类型,则将其添加到Component2List 等。

有可能吗?有什么好的方法可以做到这一点?

我可以使用类似的函数获得相同的行为:

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

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

【问题讨论】:

与vector-template-c-class-adding-to-vector相关 【参考方案1】:

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;

【讨论】:

您应该从list_of&lt;Component&gt; 内推导出的Component 中删除引用和常量/易失性 std::decay_t&lt;T&gt; 可以替换 std::remove_cv_t&lt;std::remove_reference_t&lt;T&gt;&gt; @Agop。当然。 expand 是一个整数数组的 typedef。通过展开可变参数包来扩展此数组。数组的第一个元素是 0,每个后续值都用 (void(add_component(...)), 0) 初始化。你会记得逗号运算符是 C++ 中的一个序列点,因此它将强制执行 add_component(),然后丢弃结果,然后计算 0,它进入数组。最后,整个数组都被扔掉了,所以只剩下副作用了。 @Agop 是的,就是这样,但请记住,零大小的数组在 c++ 中是非法的(在 c 中是合法的),因此如果参数包为空,使用初始零可以防止数组大小为零。 @Agop 确实是。【参考方案2】:

最直接的变体是不使用模板,而是重载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&lt;T&gt; 派生。我没有这样做,因为它会使list 的所有成员函数在MyClass 类中可用(即使不是公开的),这可能会造成混淆。 不能将addElement()函数放入基类模板中,避免派生类中的模板。简单的原因是为了addElement() 函数扫描不同的基类,然后才执行重载决议。因此,编译器只会在第一个基类中找到addElement()。 这是一个普通的 C++98 解决方案,对于 C++11,我会查看 Jens 和 Richard 建议的基于类型的元组查找解决方案。

【讨论】:

感谢这个有趣的解决方案。我使用 C++11,所以我将使用元组解决方案,但感谢您的帮助:)【参考方案3】:

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

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

【讨论】:

【参考方案4】:

如果您事先不知道在实例化多容器时需要存储的类型,则可以选择隐藏类型并使用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 抛出(由于内存不足或列表内元素的复制构造函数抛出),已经分配的列表将不会已解除分配。

【讨论】:

我很确定您可以用适当的 C++ 强制转换替换其中的每个 C 样式强制转换,这样可以保留编译器的一些检查。此外,您可以使用简单的map&lt;type_index, shared_ptr&lt;void&gt;&gt; 来存储指向相应list&lt;T&gt; 的指针(甚至可能是unique_ptr&lt;void&gt;,尽管我不确定)。但是,这并不能解决复制(如果需要的话),需要克隆功能或者可能是 Boost.Any。我仍然喜欢那个解决方案,尽管它会产生运行时开销。【参考方案5】:
void addElement(Component1 component) 
   componentList1.insert(component);


void addElement(Component2 component) 
   componentList2.insert(component);

【讨论】:

你不需要dynamic_cast,这一切都可以在编译时完成。

以上是关于C++ 模板:按类列出,如何分解代码?的主要内容,如果未能解决你的问题,请参考以下文章

c++模板和继承

我如何按类属性对C++向量数组进行排序[重复]

如何在 Eigen C++ 中加速 LU 分解?

如何使用 list.js 按类计算元素

按类访问 C++ 原始数组

如何附加到最近的 div(按类)?