C++模板:一个按类列出的列表,如何分解代码
C++ Template : one list by class, how to factorize the code?
假设我有这个类:
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);
}
相关文章:
- 通过递归进行因子分解
- Pybind11:将元组列表从Python传递到C++
- 有人能分解一下这个c++模板的语法吗
- 从链接列表c++中删除一个项目
- 如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
- C++如何通过用户输入删除列表元素
- 读取文件的最后一行并输入到链接列表时出错
- 复制列表初始化的隐式转换的等级是多少
- LNK2038、MSVS2017 MAGMA的原因列表
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 没有为自己的结构调用列表推回方法
- 使用简单类型列表实现的指数编译时间.为什么
- 函数何时会在c++中包含stack_Unwind_Resume调用
- 一对向量构造函数:初始值设定项列表与显式构造
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 通过for循环使用用户输入填充列表
- 双重列表复制构造函数:与单一列表复制构造函数有何不同
- 为什么 lambda 表达式的捕获列表无法使用结构化绑定分解
- C++模板:一个按类列出的列表,如何分解代码
- 给定一个素数列表和一个因数分解模式,如何构造其因数分解符合给定模式的所有数