boost源码剖析之:Tuple Types(rev#2)
刘未鹏(pongba)
C++的罗浮宫(http://blog.csdn.net/pongba)
Note: 并非新作,04年曾放在blog上,后来删掉了,不过网上到处有转载。这是修改之后的版本。
动机[1]
假设你有这样一个函数:它接受两个整型数据并返回它们整除的结果,像这样:
int DevideInts(int n,int d)
{
return n/d;
}
但是我们可能需要更多信息,比如,余数。函数的返回值已被占用,我们可以为函数加一个参数:
int DevideInts(int n,int d,int& Remainder)
{
Remainer=n%d;
return n/d;
}
但是这样的函数形式未免有些拖沓丑陋。我们可以使用std::pair<>来定义函数的返回值类型(顾名思义,std::pair<>可以将两个值凑成一对),像这样:
std::pair<int,int> DevideInts(int n,int d)
{
return std::pair<int,int>(n/d,n%d);
}
这是个可行的方案。简洁,优雅。
然而,这个方案只能提供两个返回值的捆绑,如果现在需要返回三个int呢?唔...你可能很快想到这样组织代码:
std::pair<int,std::pair<int,int> > someFunc();
的确,这也能够工作,但是毕竟不够精致!如果返回值再增加,代码将会愈发丑陋不堪。另一个可行的方案是自己定义一个结构来保存三个乃至更多值,然而随着不同函数的需要你可能需要定义各种不同的类似这样的结构,这太费神了。
所以,我们需要的是一个高度可复用的,能够用来保存任意型别的任意多个变量的类——Tuple Types(Tuple的意思是“元组,数组”)。正如你所想象的,泛型正是提供代码复用的最佳手段,它将型别信息抽象出来,直到用户真正使用那些代码时,型别信息才得以落实(所谓“具现化”)。
Boost库提供了所谓的Tuple Types,它没有std::pair的限制,于是你可以写:
//tuple<>目前能够支持多达10个模板参数
boost::tuple<int,int,int> someFunc();
事实上tuple能够提供的不止这个,tuple对IO流的支持能够允许你写这样的代码:
tuple<int,int,int> t(8,9,10);
std::cout<<t; //输出(8 9 10)
tuple甚至还支持类似的流控制,像这样:
std::cout << tuples::set_open(‘[‘)
<< tuples::set_close(‘]’)
<< tuples::set_delimiter(‘,’)
<< t;
//输出[8,9,10]
好了,你可能已经不耐烦了,毕竟,以上的内容非常浅显。然而我必须要告诉你这些,因为你首先得知道tuple的设计目的才能够去了解它。好在这个枯燥的过程已经结束了。深吸一口气,我们去看一看tuple的设计细节和最本质的东西——源代码。
设计目标
首先,了解tuple的设计目标十分重要。上面所讲的只是一个总的设计目标。下面两个细节设计目标才是真正需要和体现技术的地方(并且考虑它们如何能够最佳实现是非常有趣的事情,当然,在你的种种考虑之后,你得承认,Boost库的设计无疑是最精致和高效的),容我向你阐述它们:
tuple中的数据成员的个数应该具有某种动态特性。具体的说就是如果你像这样具现化tuple: tuple<int,int> t。则t某种程度上应该只需要sizeof(int)*2大小的内存来存放它的数值,不应该有多余的内存分配。而如果是tuple<int,int,int> t;则sizeof(t)某种程度上应该为sizeof(int)*3。当然,你可以利用模板偏特化来实现这一点——为提供不同模板参数个数的tuple实现不同的偏特化版本(也就是说,对提供了N个模板参数的tuple准备的偏特化版本中具有N个数据成员)——但是,想想这样做的代码数量吧!你也可以使用动态分配底层容器的策略,然而那会带来额外的负担,显然不如将数据直接放在tuple对象里,况且底层容器又该如何设计呢?事实上,boost::tuple并没有使用以上任何一种手法,它使用了一种类似Loki库[2]里的TypeList设施的手法来定义它的底层容器,这种精致的手法利用了某种递归的概念,极大的减少了代码量。后面我会为你介绍它。
tuple 必须提供某种途径以获取它内部保存的数值。类似的,通过某种编译期的递归,Boost极其巧妙地达到了这个目标。遗憾的是,由于技术上的原因,当你需要获取第N个数据时,你所提供的N必须是编译期可计算出的常量。这也体现出C++泛型缺少一些运行期的特性——是的,C++泛型几乎完全是编译期的。
其实,虽然上面我只为你描述了两个设计目标,但是实作时仍会有各种小问题出现。下面的源码剖析中我会一一为你解惑。
好吧,在你发出抱怨声之前,我还是快点转入我们的主题:
boost::tuple源码剖析
boost::tuple的实现有许多精妙之处,真是千头万绪不知从何说起。还是从一个最简单的应用展开吧:
//请记住它,后面我们将一直围绕这个例子
boost::tuple<int,long,bool> myTuple(10,10,true);
以上简单的代码的背后其实发生了很多事,了解了这些事你几乎就了解了关于tuple的一大半奥秘。首先我们肯定想知道tuple的声明是什么样子的,在boost/tuple/detail/tuple_basic.hpp中声明了它,其中也包括tuple几乎所有的实现:
template < class T0 = null_type, class T1 = null_type, class T2 = null_type,
class T3 = null_type, class T4 = null_type, class T5 = null_type,
class T6 = null_type, class T7 = null_type, class T8 = null_type,
class T9 = null_type > // null_type是个空类
class tuple; // 注意这个声明的所有模板参数都有缺省值
下面是boost::tuple的定义(也摘自boost/tuple/detail/tuple_basic.hpp):
template <class T0, class T1, class T2, class T3, class T4,
class T5, class T6, class T7, class T8, class T9>
class tuple :
public detail::map_tuple_to_cons<T0, T1, T2, T3, T4,
T5, T6, T7, T8, T9>::type
{
// tuple的定义体十分简单,其中是若干构造函数(将参数转交给基类)和模板赋值操作符
…
}; // 为了凸显重点,以下先讲tuple的基类
其实tuple本身的定义并无奥秘和技巧可言,所有秘密都藏在它的基类里面,tuple只是将参数转交给基类处理。下面我为你剖析它的基类:
基类大厦的构建
构建大厦的脚手架——map_tuple_to_cons<>
在我们给出的极其简单的应用代码中:tuple<int,long,bool> myTuple(10,10,true);其实相当于:
tuple<int,long,bool,
null_type,null_type,null_type,null_type,
null_type,null_type,null_type
> myTuple(10,10,true);
这是因为tuple的定义中所有模板参数都有缺省值,所以你没有给出值的模板参数自然会被编译器认为是缺省值null_type。这样T0,T1,...,T9分别是int,long,bool,null_type,.....null_type。你发现基类的表现方式非常怪异——是一个map_tuple_to_cons<>中的内嵌型别::type。很自然,你该知道map_tuple_to_const<>的定义,下面就是:
template <class T0, class T1, class T2, class T3, class T4,
class T5, class T6, class T7, class T8, class T9>
struct map_tuple_to_cons
{
// cons<>是数据的容器,也是所有奥秘所在
1 typedef cons<
T0, //第一个参数T0被孤立出来
typename map_tuple_to_cons< //剩下的模板参数后跟一个null_type进入下一轮
T1, T2, T3, T4, T5,T6, T7, T8, T9, null_type
>::type
> type;
};
以及它的一个特化版本:
template <> //这个特化版本是终止某种递归式的自包含定义的关键,后面你会明白
struct map_tuple_to_cons<null_type, null_type, null_type, null_type,
null_type, null_type, null_type, null_type,
null_type, null_type>
{
2 typedef null_type type;
};
就这么简单。但是它的机理却并非那么明显:上面已经知道T0,T1,...,T9被推导为int,long,bool,null_type,...,null_type(其中省略号表示null_type,下同)。因此tuple的基类:
detail::map_tuple_to_cons<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>::type
被推导为
map_tuple_to_cons<int,long,bool,null_type,...,null_type>::type
而根据map_tuple_to_cons的定义1,这其实就是:
cons< int,
typename map_tuple_to_cons<long,bool,null_type,...,null_type>::type
>
其中的
typename map_tuple_to_cons<long,bool,null_type,...,null_type>::type
再一次涉及1处的typedef,因而它被推导为
cons<long,font-size: 9pt; background
分享到:
相关推荐
类似Boost中Tuple的实现 自己编写的 请支持原创
HALCON算子函数Chapter 18: Tuple
HALCON算子函数Chapter18:Tuple参考.pdf
2. std::tuple初始化 std::tuple<int> t1(10, "Test", 3.14); 这里要注意,不是所有的C++ 11编译器都支持copy-list-initialization的方式。如下代码所示。 std::tuple<int> foo_tuple() { return {1, -1}; // ...
测量程序编制 - python 27数据类型:Tuple(元组)-概述.pptx
增强PFR 这是一个C ++ 14库,用于非常基本的反射,使您可以按索引访问结构元素,并为用户定义的类型提供其他std::tuple类的方法,而无需任何宏或样板代码。检测结果分行建立测试覆盖率更多信息开发: 主: 激励实例...
Tuple(元组) - 装包与拆包 装包与拆包 Python中,元组装包拆包是自动的,不需要任何函数,导致很多人对于函数返回值一会有括号一会没括号非常迷惑 a,b,c=1,2,3 #等价于 a,b,c=(1,2,3) print(a,type(a)) #因为等号...
Python 源码阅读:tuple元组,摘抄的资料。
2. Tuple初始化 std::tuple的初始化可以通过构造函数实现。 // Creating and Initializing a tuple std::tuple<int> result1 { 22, 19.28, "text" }; 这种初始化方式要定义各个元素的数据类型,比较繁琐,
一.基本数据类型 整数:int 字符串:str(注:\t等于一个tab键) 布尔值: bool 列表:list 列表用[] 元祖:tuple ... tuple(iterable) -> tuple initialized from iterable's items If
Tuple(元组) namedtuple(具名元组)具名元组具名元组:因为元组的局限性:不能为元组内部的数据进行命名,所以往往我们并不知道一个元组所要表达的意义,所以在这里引入了?collections.namedtuple?这个工厂函数,来...
Tuple(元组)-创建;Python创建元组;Python创建元组;
本文实例讲述了Python数据类型之Tuple元组。分享给大家供大家参考,具体如下: tuple元组 1.概述 本质上是一种有序的集合,和列表非常的相似,列表使用[]表示,元组使用()表示. 特点:一旦初始化,就不能发生改变 2....
C++ lua Kaguya 应用
Python元组Tuple基础知识点总结 #!/usr/bin/env python # -*- coding:utf-8 -*- # @Time : 2020/4/17 21:23 # @Author: xuhui # @File : Tuple.py ...tuple1 = (1, 2, 3, 4, 5) print(tuple1) # 方法二:
tuple_utility, 缺少的C++ tuple 功能 tuple_utilityC++ 元组的实用工具。tuple_map:#include"tuple_utility.hpp"int main(){ auto t = std::make_tuple(0, 1
2. tuple()函数 3. 基本操作 元 组 01 元组 元组和列表操作类似,但是元组属于不可变序列,元组创建后只可以元素覆盖不可以修改元素。元组的定义方式和列表相同,但定义时所有元素是放在一对圆括号“( )”中,用逗号...
json_tuple一定比 get_json_object更高效吗?(源码剖析).docx
此标头允许使用get,std :: tuple_size,std :: tuple_element接口直接格式化范围,容器,范围和所有类型。 例子: std::vector< int32> iv{ 1 , 2 , 3 , 5 , 7 , 11 }; auto ivf = fmt::format( " {} " , iv); ...