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);
它应该执行以下操作:
如果component
是Component1
类型,将其添加到Component1List
如果component
是Component2
类型,则将其添加到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<Component>
内推导出的Component
中删除引用和常量/易失性
std::decay_t<T>
可以替换 std::remove_cv_t<std::remove_reference_t<T>>
@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<T>
派生。我没有这样做,因为它会使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<type_index, shared_ptr<void>>
来存储指向相应list<T>
的指针(甚至可能是unique_ptr<void>
,尽管我不确定)。但是,这并不能解决复制(如果需要的话),需要克隆功能或者可能是 Boost.Any。我仍然喜欢那个解决方案,尽管它会产生运行时开销。【参考方案5】:
void addElement(Component1 component)
componentList1.insert(component);
void addElement(Component2 component)
componentList2.insert(component);
【讨论】:
你不需要dynamic_cast
,这一切都可以在编译时完成。以上是关于C++ 模板:按类列出,如何分解代码?的主要内容,如果未能解决你的问题,请参考以下文章