元组的使用

前言

在泛型开发中经常有时候需要用 一个对象 或 一个接口 对 不同的对象 进行集中处理,常见的处理方法是使用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++标准容器联用让代码的可扩展性很高。

元组的局限性也很直接,常见使用在泛型编程和一些结构体定义中、变长模板中,有时候在函数传参或字段较多的地方使用元组,字段过多会地方使用会使代码可读性不如直接定义结构体等问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值