前言
在泛型开发中经常有时候需要用 一个对象 或 一个接口 对 不同的对象 进行集中处理,常见的处理方法是使用vecotr等容器存储基类指针,通过C++的多态调用子类的虚函数进行处理。
例如下列是一个待处理类对象:
class varBase{};//基类1
class var : public varBase {};
class siteBase {};//基类2
class site : public siteBase {};
int main()
{
var Var,Var2;
std::vector<varBase*> vecBasePtr = { &Var,&Var2 }; //使用容器存储并进行统一处理
site Site,Site2;
std::vector<siteBase*> vecBasePtr_ = { &Site,&Site2 };
}
用如上述法去处理不同对象有着使用的限制,那就是从设计上,管理的对象必须继承于同一个基类对象,必须事先预留供实现的虚函数接口,如果想使用同一个模板对象或者模板函数去统一处理的话,上述的方法管理对象就不适用了。
假设上述代码中想要 统一管理 var与site对象到一个变量中去,想要让许多不同的对象都可以被同一个对象管理,不关心其是否继承于同一个基类,不更改原来的代码架构,并且可以获取其中任意一个对象的真实类型,定义起来简单明了,采用上述模式就较为复杂了,需要更改代码架构,并采用工厂模式,每增加一种对象,都需要定义一个类型,使用if else去扩展,并需要让他们继承于同一个基类,维护起来较为复杂。
所以需要一种能够 存储不同数据类型的 类型,元组就是这样一种的一种不关心存储的数据的类型的类型,很多地方可以代替结构体进行使用。
std::tuple<var,var,site,site> Tuple(Var,Var2,Site,Site2); 元组
元组是python六大基础数据类型之一,在C++11后,C++也引入了这种数据类型,需要包含头文件使用。
类似于pair这种可以存储两个不同类型的数据,tuple元组可以存储数量不定的对象,其定义如下:
template <class ... T>
class tuple
{};
元组是一个 不定参数模板 ,可以存储不同数据类型的数据。
元组的特性是元素值不可被修改。
元组是一个固定大小的不同类型值的集合,是泛化的std::pair。
元组的基础使用方法
//元组的创建
std::tuple<var,var,site,site> Tuple(Var,Var2,Site,Site2);
std::tuple<var,var,site,site> Tuple_ = std::make_tuple(Var,Var2,Site,Site2);
//获取元组数据
std::get<0> (Tuple) = var();
std::get<3> (Tuple) = site();
//获取元组的大小
size_t Num = std::tuple_size<decltype(Tuple)>::value;
//获取tuple类型
std::tuple_element<0,decltype(Tuple)>::type tmp = std::get<0> (Tuple);
//拼接元组
auto tupleCat = std::tuple_cat(Tuple,Tuple_);
在使用元组的时候,需要注意元组作为一个不定参数模板,想要获取其某个数据,必须使用constexpr类型,模板数据必须在编译时就应该确定了该模板对象的具体属性。
constexpr int i = 0;
std::get<i> (Tuple) = var();//正确
std::get<0> (Tuple) = var();//正确
int n = 0;
std::get<n> (Tuple) = var();//错误
元组的简单实现原理
template<class ...T>
class MyTuple;
template<>
class MyTuple<> {};
template<class Head,class ...Other>
class MyTuple<Head,Other...> : private MyTuple<Other...>
{
using InheritedTuple = MyTuple<Other...>;
public:
MyTuple(){}
~MyTuple(){}
MyTuple(Head h,Other...other) : m_head(h),InheritedTuple(other...) {}
Head head(){return m_head;}
InheritedTuple& tail(){return *this;} //返回基类引用
protected:
Head m_head;
};
MyTuple<int,float,char> t(1,2.0,'c');
std::cout << t.head() << std::endl;
std::cout << t.tail().head() << std::endl;
MyTuple<int,float,char>继承自->MyTuple<float,char>
MyTuple<float,char>继承自->MyTuple<char>
MyTuple<char>继承自->MyTuple<>
通俗一点理解就是递归,把第一个元素保存,剩下元素传递给基类,直至为空 。
元组的遍历
元组的遍历就稍微麻烦了,因为模板的特性,使用模板数据需要在编译时就确定元素的属性,故一般的遍历方法在元素中不适用。
for(int i = 0; i < std::tuple_size<decltype(tupleCat)>::value ; ++i )
{
std::get<i>(tupleCat) = 1; //错误
}
遍历方法同样需要使用不定参数模板
template<typename Visit>
void VisitTuple(size_t index, std::tuple<> & t, Visit v){}
template<typename T, typename ... Ts, typename Visit>
void VisitTuple(size_t index, std::tuple<T, Ts...>& t, Visit v)
{
if (index > 0)
VisitTuple(index - 1, reinterpret_cast<std::tuple<Ts...>&>(t), v);
else
v(std::get<0>(t));
}
class Vistor
{
public:
template<typename Arg>
void operator()(Arg&& arg)
{}
};
//遍历
for(int i = 0;i < std::tuple_size<decltype(m_protocolTupleObj)>::value; ++i)
{
VisitTuple(i, m_protocolTupleObj, Vistor());
}
其中VisitTuple是一个简单的递归函数,每次将元组的第一个元素出列,将剩下的元素递归,直至为0。当为0时,使用get<0>获得该元素。
reinterpret_cast<>是一种类型强转,让编译按照目标转换类型在内存中的比特位重新编译原始数据类型,转换前后对象的地址是不变的,只是对象的读取内存比例变了。可以简单理解为指针偏移。
其他一些常见的使用方法
//比较自定义结构体、类
struct S {
int n;
std::string s;
float d;
bool operator<(const S& rhs) const {
return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d); //按照第一个int n比较,然后string s,然后float d,重载了'<'符号方法的类和结构体也可以用这个方法方便的比较
}
};
//需要定义未知参数的结构体时
template<class... T>
auto function(T... t)->decltype(std::tuple<T...>(t...))
{
auto Tuple = std::make_tuple(t...);
/*...*/
return Tuple;
}
//当不想定义一些临时结构体时
std::tuple<std::string, int, int> func1()
{
std::string v1 ("nyaa");
int v2 = 1;
int v3 = 2;
return std::make_tuple(v1, v2, v3);
}
std::string v1;
int v2;
int v3;
std::tie(v1,v2,v3) = func1(); //解包
//在键值对使用元组存储多个数据
mapData.insert(std::make_pair(id,make_tuple(...)));//插入一个id-tuple
auto i = mapData[id];
std::tie(...) = i;//解包
std::tie会将函数参数打包成元组,根据元组自身的比较性质进行比较,元组本身就可以看做一个包和结构体的存储方式基本一致。
至此,元组从简单的使用方法到原理等核心机制已经描述的差不多了。
总结
本文主要总结了元组的相关基础的用法,并对其原理进行了简单的介绍,尝试简单分析了元组的运作原理。tuple是一个固定大小的、可以存储不同数据类型的集合,我们可以把它当做“匿名的”一个结构体或者一个类一样使用,它却又更加的简洁、直观、好用。
针对编程中我遇到的问题,遇到需要一种存储不同数据类型的进行数据的统一管理时,元组能够解决,并且不需要更改原有的代码架构,只需要将对象填入元组即可,需要使用时,只需要将元素从元组内提出,不需要进行继承、重新设计等一系列操作,并且元组是基于C++泛型编程思想进行设计的一种数据类型,元组和C++标准容器联用让代码的可扩展性很高。
元组的局限性也很直接,常见使用在泛型编程和一些结构体定义中、变长模板中,有时候在函数传参或字段较多的地方使用元组,字段过多会地方使用会使代码可读性不如直接定义结构体等问题。