Liu's profile饮鸩止渴PhotosBlogLists Tools Help

Liu LIU

Occupation
Location
No list items have been added yet.
There are no photo albums.

饮鸩止渴

June 01

Thank You, Paolo!!

1985年,你开始了第一场比赛,我才刚学会走路。那一年,你17岁,我3岁。

1988年,你开始为你祖国而战,我才刚上小学。那一年,你20岁,我6岁。

1989年,你获得了你的第一个欧洲冠军,我在那年认识了米兰,知道了巴斯腾和古力特。那一年,你21岁,我7岁。

1990年,你参加了你的第一次世界杯并获得了第三名,我在那年认识了你。那一年,你22岁,我8岁。

1994年,你参加了你的第二次世界杯,可惜屈居亚军;这是我第一次全程关注了世界杯也真正成为球迷。那一年,你26岁,我12岁。

1995年,你成为了意大利国家队和米兰的队长,进入初中的我继续追逐着你的消息。那一年,你27岁,我13岁。

1998年,你参加了你的第三次世界杯,对阵法国时,点球再次伤害了你;步入高中的我找到了更多志同道合的朋友,但是却睡过了你们对阵法国的比赛。那一年,你30岁,我16岁。

1999年,米兰的百年诞辰,在你的带领下,米兰反超Lazio获得了联赛冠军;而我,正在高中的生活中煎熬,从《体坛》上获得你夺冠的消息。那一年,你31岁,我17岁。

2000年,在欧洲杯上,再次输给了法国,而屈居亚军;那时正在准备高考的我,在上学路上,从收音机里听到这个消息。那一年,你32岁,我18岁。

2002年,你的最后一次世界杯,最终,你还是输给了自己的年龄;进入大学的我,本想好好享受一次世界杯,没想到却给裁判们毁了。那一年,你34岁,我20岁。、

2003年,你第一次亲手举起了欧冠奖杯,如同四十年前你父亲一样;而我在蹉跎中继续关注着米兰,关注着你。那一年,你35岁,我21岁。

2005年,虽然你创造了欧冠决赛的最快进球,但是那诡异的6分钟……而我,躺在床上,通过小叶的短信直播渡过了这个难熬的一晚。那一年,你37岁,我23岁。

2006年,你曾经挚爱的国家队获得了大力神杯;而我永远记住了那年的6月26日,永远记住了键翔的激情。那一年,你38岁,我24岁。

2007年,在雅典,你第二次举起了你的第5个欧冠奖杯;我没有看这场决赛,因为我相信“出来混,总是要还的”。那一年,你39岁,我25岁。

2008年,你参加了你第1000场正式比赛,而我对于你再踢一年的决定感到很兴奋,因为我无法想象没有你的米兰。那一年,你40岁,我26岁。

2009年,你终于还是要告别了,就在昨晚,在佛罗伦萨,你的1041次比赛;我在网络上看了这场比赛,对于比分我不关注,我只想见证一个伟大的历史时刻,虽然这不是我想要的。这一年,你41岁,我27岁。

你24年的职业生涯,N年如一日的努力与忠诚,经历了各种成功,也有过各种失败。没有完美的人,只有完美的保罗……

当年,因为米兰,我知道了你;但却是因为了你,我喜欢了米兰。如今,喜欢你和喜欢米兰已经是我生活中的一部分。

多年前,有个愿望,就是能去圣西罗去看一次你踢球,不过目前看来已经不大可能实现了。

你的故事就是我的荣耀,谢谢你,保罗!!

May 15

详解C++预处理器(草)

学了C++许多年了,代码看了不少,也写了不少,各种预处理指令也都使用过,但是从来就没有深入的去探索它的本质。最近在看boost的源码,惊诧于boost::preprocessor,于是想更多的了解一下,然而主流的C++教程上对于预处理的描述不是很详细,网络上也很难搜索到完整的描述,于是想自己搞明白它,选择了 C++STD 2003作为参考,使用MSVC来测试。

1 翻译代码的各个阶段

把C++源码从源文件的方式变成可执行的二进制文件通常需要三个主要步骤:预处理,编译,链接;这里的翻译就是指这个整个过程。对于源文件的处理,有个最基本的单位——翻译单元(有些也称编译单元),也就是:一个源文件,加上所有使用#include包含的文件,再去掉所有由条件编译指令去掉的代码组成一个翻译单元。

那么下面是各个阶段的描述(编译器的实现可能会同时处理多个阶段):

1) 按照编译器自己的实现,把物理源文件中的字符映射到编译器内部的基本字符集(为行结束符引入换行符)。使用单个字符替换Trigraph序列(下面描述)。使用统一字符名称替换不在基本字符集中的字符。(实现可以使用任何内部编码,只要能够保证源文件中实际遇到的扩展字符与源文件中代表同一个扩展字符的统一字符名称(如使用 \uXXXX 标记)被等价处理即可。)

Trigraph

Replacement

Trigraph

Replacement

??=

#

??!

|

??/

\

??<

{

??’

^

??>

}

??(

[

??-

~

??)

]

   

      注意:这些实在翻译的第一步就替换的,比宏替换的优先级高。

2) 删除所有的前面有’\’的换行符,分割物理行为逻辑行。如果此时产生了一个统一字符名称的话,行为是未定义的。如果一个非空源文件不是以一个换行符结尾,或者以一个前面有’\’的换行符结尾的话,行为是未定义的。

3) 把源文件分解为预处理标记和一些空白字符序列(包含注释)。一个源文件不能以部分预处理标记或者部分注释结束。每个注释会被一个空白字符替代,换行符会保留。是否将不含换行符的空白字符序列替换为一个空白字符将由编译器的具体实现确定。分割源文件字符为预处理标记的过程是上下文相关的。例如:在#include指示符下处理<。

4) 执行预处理指示符,并展开宏调用。如果标记连接生成了一个统一字符名称的话,行为是未定义的。#include指示符会使被包含的头文件或源文件递归的进行1到4阶段的处理。

5) 每个源码的字符集成员、转义序列、统一字符名称和字符串的字符量被转换为执行字符集的成员。

6) 连接相邻的普通字符串,连接相邻的宽字符串。

7) 空白字符分割标记不再有效,每个预处理标记被转换为一个标记。这些结果标记是在语法和语义上分析很翻译过的。【注意:源文件,翻译单元和被翻译过的翻译单元不需要存储在文件中,也不需要和外部表示有一一的对应关系。这些描述仅是概念上的,并没有指定具体的实现】

8) 已翻译的翻译单元和实例化单元(模板)按如下方式组合:检测每个已翻译单元来生成一个需要实例化的实例化列表【这些可能包含显示实例化请求】。定位所需要的模板定义,此时是否需要这些定义的源文件是有具体编译器觉得的【注意:一个具体实现应该在已翻译单元中保存足够的信息,来确保这里不需要源码】。执行实例化列表中的所有实例化操作,生成实例化单元【注意:这和已翻译单元很类型,但是不包括对未实例化模板的引用,也不包括模板定义】。

9) 决议所有的外部对象和函数引用。将库组件链接进程序,以满足当前翻译单元中未定义的函数和对象的外部引用。所有这些翻译器输出被汇集成一个程序映像,此映像包含在其执行环境中执行所需要的信息。

2 预处理指示符

预处理指示符包含一个序列的预处理标记。第一个标记是#,它可以是文件的第一个字符或者前面有至少一个的换行符。序列的最后一个字符是一个换行符。

预处理指示符主要包括:条件包含指示符,文件包含指示符,宏定义指示符,行操作指示符,错误指示符,Pragma指示符和空指示符。

对于宏定义指示符,还会包含宏替换的相关定义,这是需要讨论的重点。

2.1条件包含指示符

形式如下:

#if constant-expression

// code

#elif constant-expression

// code

#else

// code

#endif

根据提供的constant-expression是否为非0,来回控制代码是否参与编译。如果constant-expression中有宏,那么在宏替换后再进行求值(除非宏由defined修饰,形式为defined identifier或者defined (identifier),如果identifier已经定义为一个宏名称,值为1,否则为0)。如果宏展开生成了一个defined,或者对于defined的使用不满足优先于宏替换的两种特定形式的话,产生的行为是未定义的。

一些其他的形式:

#ifdef macro-name // 相当于#if defined

#ifndef macro-name // 相对于#if !defined

2.2 文件包含指示符

用于显示的包含特定的头文件或源文件。

形式如下:

#include<filename>

#include”filename”

#include macro-name // macro-name可能展开为以上两种形式

不管使用什么方式指定包含,只要形式正确,指定的文件能够找到,那么就是使用目标文件的所有内容来替换此指示符。在第三种形式下,如果macro-name不能替换成上面两种形式,那么产生的行为是未定义的。

2.3 行指示符

用于指定当前编译位置的行值,及当前编译的源文件名。

形式如下:

#line digit-seq

#line digit-seq “filename”

#line macro-name // macro-name可能展开为以上两种形式

在此指示符之后所有的源码就把digit-seq对应的数字作为起始行。如果digit-seq为0或者大于32767,行为是未定义的。第二种形式还能指定后面的源码属于特定的文件。在第三种形式下,如果macro-name不能替换成上面两种形式,那么产生的行为是未定义的。

2.4 错误指示符

形式如下:

#error pp-tokens

用于生成一个包含特定预处理标记序列的错误信息,报告程序的形式错误。

2.5 pragma指示符

形式如下:

#pragma pp-tokens

用于实现一些特定编译器的行为,如果编译器不支持,此指示符将被忽略。

2.6 空指示符

形式如下:

#

这种形式将不产生任何效果。

2.7 宏定义指示符

定义一个宏,用于后面的defined测试或者宏替换。

形式如下:

#define macro-name // 定义一个宏,替换为空,一般用于defined测试

#define macro-name replacement-list // 定义一个宏,将使用后来的列表来替换

#define macro-name(para-list) replacement-list // 定义一个宏函数,且使用指定参数来替换

对于宏替换的一些注释:

1) 当且仅当预处理标记有相同的数目、顺序、拼写和空白分割(所有的空白分割被认为是一样的)时,两个替换列表才一样。

2) 一个标志符被定义为一个不使用左括号的宏(类似于对象的宏)可能会被其他#define指示符提供定义,如果两个定义的替换列表不一样,此程序是ill-formed。

3) 一个标志符被定义为一个使用左括号的宏(类似于函数的宏)可能会被其他#define指示符提供定义,如果他们使用的参数数目和拼写相同,替换列表相同那么是可行的,否则程序是ill-formed。

4) 调用一个类似于函数的宏,提供的实参必须符合宏的定义。必须以)标识调用结束。

5) 一个类似于函数的宏中的参数必须在定义域内唯一。

6) 紧跟在define后面的标识符为宏名称,并且所有的宏都属于一个名字空间。宏名称后预处理字段前面或后面的空白字符不属于替换列表也不属于宏定义的形式。

7) 如果#标记后面跟着一个标志符,那么此标志符不参与宏替换。

8) 形如第一种的宏定义,是定义一个类似于对象的宏,那么后面的宏名称的实例会被宏名称后面余下的预处理标记替换。替换后,会再次扫描是否还有宏名称。

9) 形如第二种的宏定义,是定义一个类似于函数的宏。它的参数的定义域为:从声明的位置开始,到宏定义结束位置结束。后面的每个此宏名称的且跟着(的实例将会引入被定义中替换列表替换的预处理标记序列(也就是调用宏)。这个替换的预处理标记序列在匹配最外部的)时终止(会跳过内部相互匹配的左括号和右括号)。在有宏调用形成的预处理标记序列中,换行符被考虑为一个普通的空白字符。

10) 在宏调用中由最外层的括号包围着的预处理标记是实参列表,他们由逗号分隔(但是在内部括号中的逗号不分割实参)。如果一个实参不包含预处理标记(为空),那么行为是未定义的。如果实参列表中有表现为预处理指示符的预处理标记,那么行为是未定义的(参数中有#if等)。

一些例子:

// 以下是合法的

#define OBJ_LIKE (1-1)

#define OBJ_LIKE /* white space */ (1-1) /* other */

#define FTN_LIKE(a) ( a )

#define FTN_LIKE( a )( /* note the white space */ \

a /* other stuff on this line

*/ )

// 一下是不合法的

#define OBJ_LIKE (0) /* 不同的标记序列 */

#define OBJ_LIKE (1 - 1) /* 不同的空格 */

#define FTN_LIKE(b) ( a ) /* 使用的形参不同 */

#define FTN_LIKE(b) ( b ) /* 不同的形成拼写 */

2.7.1 实参替换

在宏函数调用时,确认实参之后,实参替换就会发生。替换列表中的形参,除了前面有#或者##,或者后面有##的,都会被对应的实参(如果实参中有宏,则需要先展开里面的宏)替换。

2.7.2 #操作符

它是指:在宏函数定义的替换列表中,后面紧跟一个形参的#预处理标记。

替换列表中,如果形参前面紧接着又#,那么他们全部会被一个字符串替换,此字符串的内容为对应的实参序列。实参序列中每个连续的空白字符串都会被替换为单个空白字符,并且它前面或者后面的空白字符将被删除。一般的,原来的实参拼写,会出现在替换后的字符串中,除了一些特别的处理:在\或”前插入\字符。如果替换结果是一个不合法的字符串,那么产生的行为是未定义的。计算#和##的顺序是未指定的。

示例:

#define STR_I(x) #x

#define STR(x) STR_I(x)

// 这里需要有一层间接,

// 因为在没有间接的情况下,如果传入的实参是个宏,

// 那么就会直接输出宏名称组成的字符串(根据2.7中的7)

STR(10); //”10”

STR( 101010 1010 ); // “101010 1010”

STR(“”); //”\”\””

STR(\\); //”\\”

STR(“); // 错误,在进行第三阶段时,会进行预处理标记的分割,

// 这样的分割不能形成宏定义

STR(\); // 错误,)不能转义,不能完成宏定义。

2.7.3 ##操作符

##标记不能出现在替换列表的开头或结尾。

替换列表中,如果形参前面或者后面紧跟着一个##操作符,那么此形参会被对应的实参序列替换。对于类似于对象的宏和类似于函数的宏,在再次检测替换列表中是否还有宏之前,每个##实例是被删除,前面的标记会和后面的标记链接起来(也就是会忽略之前的空白字符),替换的结果还能参与后面的宏替换。如果替换的结果不是一个合法的标记,行为是未定义的。##操作符的计算顺序是未指定的。

示例:

#if defined __MSVC__

// msvc对于再次扫描宏的支持不好

// 需要增加一层间接。

// 对于JOIN_I(x, y),如果结果xy中含有宏,msvc在很多情况下是不会再展开的

// 所有只有把结果在传入到一个宏函数中去,迫使它继续展开。

#define JOIN_I2(x) x

#define JOIN_I(x, y) JOIN_I2(x##y)

#else

// 其他编译器貌似没有这个情况,具体参见:boost\preprocessor\cat.hpp

#define JOIN_I(x, y) x##y

#endif

#define JOIN(x, y) JOIN_I(x, y)

2.7.4 再次扫描和继续替换

宏函数调用后,所有的形参都被实参序列替换后,会再次扫描结果,并执行宏替换(如果存在的话),这样的动作会递归的执行下去,直到没有宏替换为止。

如果在结果序列(注意是当前宏被替换的结果序列中)中找到已经被替换的宏(并不是在源码中出现的,一般是替换产生的),那么这个宏不会被替换。更加的,任何嵌套的替换,如果遇到已经被替换的宏(同上),也不会展开这个宏。这些不会被展开的宏将永远不会参与替换,即使在后面的上下文中检测到可以替换。

完整宏替换后的结果不会再作为预处理指示符,即使它包含。

示例:

// 使用上面两个示例

using namespace std;

cout<<STR(JOIN(a, b));// 输出:ab (1)

cout<<STR(JOIN(JOIN(a, b), c)); //输出:abc (2)

cout<<STR(JOIN(JOIN, (a, b))); //输出JOIN(a, b) (3)

对于(2),这里虽然是嵌套,但是,由于是嵌套调用,实参中的宏会先展开,在展开后并没有生成宏。

对于(3),JOIN是宏函数,所以内部的JOIN并不是对宏函数的调用,仅展开外面的那个JOIN,得到的结果是JOIN(a, b),虽然,他是一个宏,但是他是由刚刚展开JOIN(x, y)获得,所以不能在展开。进而,对于JOIN(a, b)(这个不能展开的宏),调用STR(x),虽然在STR(x)的上下文中x(如果是宏或者包含宏)是可以展开的,但是由于前面的原因,JOIN(a, b)还是不能被展开。

2.7.5 宏的范围

一个宏定义会持续到#undef指示符或者翻译单元结束。

对于#undef标识取消一个宏的定义。如果指定的宏还没有定义,此指示符会被忽略。

3 一些预定义的宏

预定义的宏

意义

__LINE__

当前代码的行数

数字常量

__FILE__

当前的源文件名称

字符串

__DATE__

翻译源文件的日期

Mmm dd yyyy

__TIME__

翻译源文件的时间

hh:mm:ss

__STDC__

说明此程序兼容ANSI C标准

是否定义和值由具体编译器实现决定。

__cplusplus

编译一个C++的翻译单元

199711L

这些预定义的宏不能被再次定义或取消定义,否则行为是为定义的。

4 总结

预处理器的存在,是代码可移植的基础,当然这些可以通过条件包含和宏定义来实现。而宏替换一直被人所鄙视(被认为是万恶之源~~),它会让代码变得难以分析,无法调试。但是在有些方面(比如:要定义N个类模板,分别支持N个参数的情况,使用宏的递归特性就能很快实现),宏能减少代码量(尤其是那些重复的代码),在某种程度上提供可阅读性(比如MFC的消息映射,没有那些宏的话,看起来将会非常复杂),提高运行效率(没有函数调用的消耗,当然可以用内联函数来替代)。

May 10

boost::bind源码分析

boost::bind是标准库中的bind1st和bind2nd的更加通用的实现,它支持任意的函数对象,函数指针或者成员函数指针。它还能把特定值绑定到任意的参数上,也能绑定任意输入参数作为函数执行的实参。它不需要用户提供函数的返回值及其参数的类型,那些都可以通过模板演绎获得。

一、用法

1 绑定到函数或函数指针

假定现在有如下函数:

int f(int a, int b)

{

return a + b;

}

int g(int a, int b, int c)

{

return a + b + c;

}

那么bind(f, 1, 2)()相当于f(1, 2),同样bind(g, 1, 2, 3)()相当于g(1, 2, 3)。这里我们使用bind绑定了函数和函数所有的参数。bind(…)函数返回的是一个函数对象(其实是bind_t结构)。

那么如果只想绑定一部分或者某个参数的话,需要使用占位符(placeholder,表示此参数绑定第N个输入的参数),那么,bind(f, _1, 5)(x)相当于f(x, 5),这里面参数_1就是占位符(定义见附录)。

当然还有以下的:

bind(f, _2, _1)(x, y); // f(y, x)

bind(g, _1, 9, _1)(x); // g(x, 9, x)

bind(g, _3, _3, _3)(x, y, z); // g(z, z, z)

bind(g, _1, _1, _1)(x, y, z); // g(x, x, x)

如果需要绑定某个实参的引用的话,可以使用boost::ref或boost::cref来使得bind后的函数对象保存一个引用而不是一份拷贝。

2 绑定到函数对象

bind接受任意的函数对象类型,但是通常情况下,用户需要显示指定返回类型。如:

struct F

{

int operator()(int a, int b) { return a - b; }

bool operator()(long a, long b) { return a == b; }

};

F f;

int x = 104;

bind<int>(f, _1, _1)(x); // f(x, x), i.e. zero

在默认情况下,bind生产的函数对象内部保存了一个目标函数对象的拷贝,但是如果目标函数对象是不能拷贝或者拷贝的消耗太大或者有状态的的话,需要使用boost::ref或boost::cref来是bind保存一份引用,而不是拷贝。当然,这时候,用户需要确保,目标的函数对象在使用前还存在。例如:

struct F2

{

int s;

typedef void result_type;

void operator()( int x ) { s += x; }

};

F2 f2 = { 0 };

int a[] = { 1, 2, 3 };

std::for_each( a, a+3, bind( ref(f2), _1 ) ); // f2.s == 6

 
3 绑定到成员函数指针

bind可以绑定到成员函数指针,使用方法同样是把成员函数指针作为第一参数。然而,成员函数指针需要有对象才能执行,所以对象作为第二个参数。

也可以使用boost::mem_fn(),把成员函数指针包装为一个函数对象。然后使用前面绑定函数对象的方法来使用。如:

bind(&X::f, args)

// 也就是

bind<R>(mem_fn(&X::f), args) // 因为是函数对象,需要手动指定返回值

关于mem_fn的介绍可以参阅boost文档。(其源码分析之后完成)

具体的使用如下:

struct X

{

bool f(int a);

};

X x;

shared_ptr<X> p(new X);

int i = 5;

bind(&X::f, ref(x), _1)(i); // x.f(i)

bind(&X::f, &x, _1)(i); //(&x)->f(i)

bind(&X::f, x, _1)(i); // (internal copy of x).f(i)

bind(&X::f, p, _1)(i); // (internal copy of p)->f(i)

4 bind的嵌套

bind可以嵌套,也就是可以使用bind来绑定一个bind对象。如:

bind(f, bind(g, _1))(x); // f(g(x))

 

上面的例子中bind(g, _1)(x)将先计算,然后计算bind(f, g(x))(x)。

二、源码分析

1 boost::bind函数

这个函数是boost对外提供的接口,所有的绑定工作都有它们来完成。这里说它们是因为,bind是一个重载函数的集合,我们在使用时C++使用重载解析找到一个确定的函数来调用。

下面分别通过各种函数子类型分别解释(我们都是有2个参数的函数,对于成员函数指针,也是两个参数(即bind需要3个参数)):

  • 对于函数对象的重载版本

源码如下:

_bi::bind_t<R, F, typename _bi::list_av_2<A1, A2>::type> bind(F f, A1 a1, A2 a2)

{

// 就是list2<add_value<A1>::type, add_value<A2>::type >

// 对于add_value的模板元函数的描述见附录。

typedef typename _bi::list_av_2<A1, A2>::type list_type;

return _bi::bind_t<R, F, list_type> (f, list_type(a1, a2));

}

add_value是一个模板元函数,用来获取保存类型A的参数的值包装类型。对于一般的类型为value<A>,然而对于值包装类型、引用包装类型、占位符类型、bind生产的函数对象类型,结果是原来类型本身。这里可以知道,绑定函数对象使用value<A>来保存绑定的实参。

list_type是一个列表类型,在源码中就是list2<add_value<A1>::type, add_value<A2>::type >,它其实就是用来保存供F使用的所有参数(可能是bind的函数对象、占位符、值包装、引用包装等等)的列表。

最后bind函数通过传入的参数,构造了一个bind_t对象返回给用户。通过构造可以看到,其实bind_t对象维护了两个数据:f(函数对象)和list_type参数列表

 

  • 对于函数指针的重载版本

源码如下:

template<class R, class B1, class B2, class A1, class A2>

_bi::bind_t<R, R (*) (B1, B2), typename _bi::list_av_2<A1, A2>::type>

bind(R (*f) (B1, B2), A1 a1, A2 a2)

{

typedef R (*F) (B1, B2);

typedef typename _bi::list_av_2<A1, A2>::type list_type;

return _bi::bind_t<R, F, list_type> (f, list_type(a1, a2));

}

其实这里是把函数指针当成了函数对象,其余的很函数对象的版本一样。

 

  • 对于成员函数指针的重载版本

源码如下:

template<class R, class T,

class B1, class B2,

class A1, class A2, class A3>

_bi::bind_t<R, _mfi:: mf2<R, T, B1, B2>, typename _bi::list_av_3<A1, A2, A3>::type>

bind(R (T::*f) (B1, B2), A1 a1, A2 a2, A3 a3)

{

typedef _mfi:: mf2<R, T, B1, B2> F;

typedef typename _bi::list_av_3<A1, A2, A3>::type list_type;

return _bi::bind_t<R, F, list_type>(F(f), list_type(a1, a2, a3));

}

// 源码是宏展开后的(这里不考虑__cdecl或者__stdcall等)

通用,这里把F包装成了一个函数对象_mfi:: mf2<R, T, B1, B2>对象。然后按照函数对象的方法处理。

 

  • 成员数据指针的支持

可以支持绑定到成员数据指针,这里略,不是重点。

 

2 bind函数的返回类型(bind_t)

使用bind其实是调用了boost::bind系列函数的某一个重载。而他的返回类型是一个bind_t类模板的某个实例的对象,他是一个函数对象。

此类模板的定义为:

// R是返回类型,F是函数对象类型,L是参数列表类型(保存绑定的实参或者占位符)

template<class R, class F, class L>

class bind_t

{

public:

typedef bind_t this_type;

bind_t(F f, L const & l): f_(f), l_(l) {}

typedef typename result_traits<R, F>::type result_type;

//后面是到10个参数的operator()重载,

// 这里只写了2个参数的重载。

template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2)

{

list2<A1 &, A2 &> a(a1, a2);

BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);

}

template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) const

{

list2<A1 &, A2 &> a(a1, a2);

BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);

}

//…省略部分代码

// 使用给定的参数列表计算函数值

template<class A> result_type eval(A & a)

{

BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);

}

// // 使用给定的参数列表计算函数值

template<class A> result_type eval(A & a) const

{

BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);

}

template<class V> void accept(V & v) const

{

using boost::visit_each;

BOOST_BIND_VISIT_EACH(v, f_, 0);

l_.accept(v);

}

bool compare(this_type const & rhs) const

{

return ref_compare(f_, rhs.f_, 0) && l_ == rhs.l_;

}

private:

F f_;

L l_;

}

这里可以明显的看到,它维护了两个数据成员,如前面所说的:f(函数对象)和list_type参数列表。

注意到,这个类型,他主要是提供了仿函数的接口(operator()),它把主要的工作都交给了参数列表了(l_(type<result_type>(), f_, a, 0);),而list2<A1 &, A2 &> a(a1, a2);是生成了用户传入的实参的列表,都使用引用。

3 listN类型

惯例,只考虑两个参数的情况,即list2:

template< class A1, class A2 > class list2: private storage2< A1, A2 >

{

private:

typedef storage2< A1, A2 > base_type;

public:

list2( A1 a1, A2 a2 ): base_type( a1, a2 ) {}

A1 operator[] (boost::arg<1>) const { return base_type::a1_; }

A2 operator[] (boost::arg<2>) const { return base_type::a2_; }

A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; }

A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; }

template<class T> T & operator[] (_bi::value<T> & v) const { return v.get(); }

template<class T> T const & operator[] (_bi::value<T> const & v) const { return v.get(); }

template<class T> T & operator[] (reference_wrapper<T> const & v) const

{ return v.get(); }

template<class R, class F, class L>

typename result_traits<R, F>::type operator[] (bind_t<R, F, L> & b) const

{ return b.eval(*this); }

template<class R, class F, class L>

typename result_traits<R, F>::type operator[] (bind_t<R, F, L> const & b) const

{ return b.eval(*this); }

template<class R, class F, class A> R operator()(type<R>, F & f, A & a, long)

{

return unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);

}

template<class R, class F, class A> R operator()(type<R>, F const & f, A & a, long) const

{

return unwrapper<F const>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);

}

template<class F, class A> void operator()(type<void>, F & f, A & a, int)

{

unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);

}

template<class F, class A> void operator()(type<void>, F const & f, A & a, int) const

{

unwrapper<F const>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);

}

template<class V> void accept(V & v) const

{

base_type::accept(v);

}

bool operator==(list2 const & rhs) const

{

return ref_compare(base_type::a1_, rhs.a1_, 0) &&

ref_compare(base_type::a2_, rhs.a2_, 0);

}

};

注意到,此类是从storage2派生的,而此类本身并没有保存数据,那么可以猜测到,storage2是保存了具体的数据的。不过这个后面讨论。

然后,我们注意到,他也实现了仿函数接口,那么前面的bind_t类型的实现里面就是使用了这个接口(l_(type<result_type>(), f_, a, 0);)。也就是调用了template<class R, class F, class A> R operator()(type<R>, F & f, A & a, long)的某个实例。

接着注意此函数的实现,这是真正调用所在:

return unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);

注意到这里面有a[base_type::a1_]这个很诡异的取值调用。这个其实就是实现了参数绑定的关键。我们知道,a是bind_t调用时用户传入的实参组成的列表,是listX<…>(X为0,1,2)类型,当前的this是list2<…>类型,他们可能是相同的类型,也可能不同,当然这个不重要。这里只有考虑他们的基类storage类模板

对于a类型的基类来说,他保存了传入的具体实参的值,a1_、a2_...就是具体参数的值,它里面没有占位符,没有bind_t对象,没有值包装或引用包装,它只保存了传入的实参。

对于当前的(bind_t中的l_)来说,他保存了绑定的实参(值包装或引用包装,bind_t对象,占位符等),那么base_type::a1_的类型可能就是这几种之一。

那么调用时,它是怎么或准确的参数呢?首先注意到上面那些operator[]的重载,这些完成了这些功能。对于占位符:A1 operator[] (boost::arg<1>)重载,如果传入的值是占位符类型,返回占位符对应的值,1对于a1_,2对应a2_。对于值包装:template<class T> T & operator[] (_bi::value<T> & v) const,如果传入的是一个值包装,那么就取出这个值包装所包装的值,注意,这个调用和调用此操作符的对象无关,只和传入的值包装相关,这个也就是bind能使用绑定实参的关键所在。对于引用包装:template<class T> T & operator[] (reference_wrapper<T> const & v) const,和值包装类似,通用只与引用包装相关。对于bind_t对象,表达式b.eval(*this);表示使用当前的列表先进行计算求值,然后返回结果,这是bind能嵌套的原因,也是嵌套时先计算内层的原因。

最后,看到有这个unwrapper<F>::unwrap(f, 0)东西,它是把可能存在的函数包装去掉(主要去掉函数对象的引用包装,具体见附录)。

三、一些总结

在绑定一个函数对象时,它并不进行能否使用这么些参数执行的检查,而在最后真正调用的时候通过C++的底层来检查。对于,绑定到函数指针或者成员函数指针时,bind重载要求提供的参数和目标函数的参数一样(成员函数需要多一个,类对象)。

对于需要在执行中绑定到不同的函数时,对bind的第一个参数(目标函数)使用占位符是没有用的,这个情况需要使用boost::apply来处理,把要绑定的模板函数当成参数出入(也可以使用占位符),例如:

typedef void (*pf)(int);

std::vector<pf> v;

// 需要显示提供返回值类型

std::for_each(v.begin(), v.end(), bind(apply<void>(), _1, 5));

还有,注意到bind_t对象没有提供赋值运算符的重载,也就是,一旦确定了一种绑定发放,就不能修改了。

四、附录

A 占位符(placeholder)

定义为:

/// in bind\arg.hpp

template< int I > struct arg

{

arg()

{

}

// 拷贝构造函数

// 仅仅在T为占位符,且序号与I相同的时候有效。

template< class T > arg( T const & /* t */ )

{

// static assert I == is_placeholder<T>::value

// I == is_placeholder<T>::value是一个静态断言,

// 如果不满足,声明为 char [-1],这是不合法的

typedef char T_must_be_placeholder[ I == is_placeholder<T>::value? 1: -1 ];

}

};

/// in bind\placeholders.hpp

// 定义供用户使用的占位符对象,静态的。

static boost::arg<1> _1;

static boost::arg<2> _2;

static boost::arg<3> _3;

static boost::arg<4> _4;

static boost::arg<5> _5;

static boost::arg<6> _6;

static boost::arg<7> _7;

static boost::arg<8> _8;

static boost::arg<9> _9;

B value<T>模板和add_value<T>模板元函数

value<T>模板,定义如下:

template<class T> class value

{

public:

value(T const & t): t_(t) {}

T & get() { return t_; }

T const & get() const { return t_; }

bool operator==(value const & rhs) const

{

return t_ == rhs.t_;

}

private:

T t_;

};

add_value<T>模板元函数,定义如下:

template< class T, int I > struct add_value_2

{

typedef boost::arg<I> type;

};

template< class T > struct add_value_2< T, 0 >

{

typedef _bi::value< T > type;

};

// 是占位符的,定义为占位符类型,否则进行值包装。

template<class T> struct add_value

{

typedef typename add_value_2< T, boost::is_placeholder< T >::value >::type type;

};

// 下面是一些特化

// 已经是值包装了,不用再次包装

template<class T> struct add_value< value<T> >

{

typedef _bi::value<T> type;

};

// 引用包装的值包装是引用包装本身

template<class T> struct add_value< reference_wrapper<T> >

{

typedef reference_wrapper<T> type;

};

// 不包装占位符

template<int I> struct add_value< arg<I> >

{

typedef boost::arg<I> type;

};

template<int I> struct add_value< arg<I> (*) () >

{

typedef boost::arg<I> (*type) ();

};

// bind_t对象不包装

template<class R, class F, class L> struct add_value< bind_t<R, F, L> >

{

typedef bind_t<R, F, L> type;

};

C unwrapper<F>模板

主要用于取消对函数对象的引用包装。定义如下:

template<class F> struct unwrapper

{

// 一般的,直接返回对象本身

static inline F & unwrap( F & f, long )

{

return f;

}

// 返回被引用包装的对象

template<class F2> static inline F2 & unwrap( reference_wrapper<F2> rf, int )

{

return rf.get();

}

// 返回成员对象

template<class R, class T> static inline _mfi::dm<R, T> unwrap( R T::* pm, int )

{

return _mfi::dm<R, T>( pm );

}

};

D reference_wrapper<T>模板

类型T的引用包装,定义如下:

// 包装一个对象,仅保存他的引用。(其实保存的是指针)

template<class T> class reference_wrapper

{

public:

typedef T type;

// 使用boost::addressof来取址,防止类型T重载了operator&

explicit reference_wrapper(T& t): t_(boost::addressof(t)) {}

operator T& () const { return *t_; }

T& get() const { return *t_; }

T* get_pointer() const { return t_; }

private:

T* t_;

};

// 辅助函数,返回对象t的引用包装

template<class T> inline reference_wrapper<T> BOOST_REF_CONST ref(T & t)

{

return reference_wrapper<T>(t);

}

template<class T> inline reference_wrapper<T const> BOOST_REF_CONST cref(T const & t)

{

return reference_wrapper<T const>(t);

}

E storageN<…>模板

同样以N=2为例。定义如下:

template<class A1, class A2> struct storage2: public storage1<A1>

{

typedef storage1<A1> inherited;

storage2( A1 a1, A2 a2 ): storage1<A1>( a1 ), a2_( a2 ) {}

template<class V> void accept(V & v) const

{

inherited::accept(v);

BOOST_BIND_VISIT_EACH(v, a2_, 0);

}

A2 a2_;

};

template<class A1, int I> struct storage2< A1, boost::arg<I> >: public storage1<A1>

{

typedef storage1<A1> inherited;

storage2( A1 a1, boost::arg<I> ): storage1<A1>( a1 ) {}

template<class V> void accept(V & v) const

{

inherited::accept(v);

}

static boost::arg<I> a2_() { return boost::arg<I>(); }

};

template<class A1, int I> struct storage2< A1, boost::arg<I> (*) () >: public storage1<A1>

{

typedef storage1<A1> inherited;

storage2( A1 a1, boost::arg<I> (*) () ): storage1<A1>( a1 ) {}

template<class V> void accept(V & v) const

{

inherited::accept(v);

}

static boost::arg<I> a2_() { return boost::arg<I>(); }

};

May 07

boost::function源码分析

boost::function通过宏和模板来实现了自己的仿函数对象,完成了对四种不同类型函数子(Functor)的包装,实现了一个泛型函数,理论上能够支持任意多参数的任何函数。

一、用法

详细见boost的function文档(boost function tutorial),这里略。

二、源码分析

可以在它的指南上看到有两种使用的方法:

Preferred syntax

Portable syntax

boost::function<float (int x, int y)> f;

boost::function2<float, int, int> f;

前面一种是更容易理解的格式,而后面一种是更具有移植性的格式(某些编译器可能不支持模板部分特化)。

在源码中function的定义如下:

template<typename Signature, typename Allocator = std::allocator<void> >

class function;

当然这是trivial的,我们具体使用到的是继承与functionN,并且进行部分特化后的版本,如:

boost::function<float (int x, int y)>对应的模板定义应该是:

template<typename R, typename T0, typename T1, typename Allocator>

class function<R (T0, T1), Allocator>

: public function2<R, T0, T1, Allocator> {}

// 源码中这些都是不存在的,因此为了减少代码量,boost大量使用了宏

1. function模板

下面为了说明的简便,使用function<R (T0)>来说明。

那么此模板在宏展开后大概是(简便期间略去了访问限制):

template<typename R, typename T0, typename Allocator>

class function<R (T0), Allocator>

: public function1<R, T0, Allocator>

{

typedef function1<R, T0, Allocator> base_type; // 基类类型

typedef function self_type; //本身类型(属于插入式类型,不用使用模板参数)

struct clear_type {}; // 用于赋0值时的重载解析

typedef typename base_type::allocator_type allocator_type;

// 默认构造函数

function() : base_type() {}

template<typename Functor>

function(Functor f

// 下面这个参数是指在Functor不是int时时有效的,也就是防止了

// 传入null构造的情况

,typename enable_if_c<

(boost::type_traits::ice_not<

(is_integral<Functor>::value)>::value),

int>::type = 0)

: base_type(f)

{

}

// 如果传入的是null,那么会调用这个函数

function(clear_type*) : base_type() {}

// 拷贝构造函数

function(const self_type& f) : base_type(static_cast<const base_type&>(f)){}

// 根据函数对象构造

function(const base_type& f) : base_type(static_cast<const base_type&>(f)){}

// 下面是一个复制运算符,主要也是处理的赋null的情况

self_type& operator=(const self_type& f)

{

self_type(f).swap(*this);

return *this;

}

template<typename Functor>

typename enable_if_c<

(boost::type_traits::ice_not<

(is_integral<Functor>::value)>::value),

self_type&>::type

operator=(Functor f)

{

self_type(f).swap(*this);

return *this;

}

self_type& operator=(clear_type*)

{

this->clear();

return *this;

}

self_type& operator=(const base_type& f)

{

self_type(f).swap(*this);

return *this;

}

}

这里可以看到,function其实是继承了functionN,他本身并没有什么主要的功能,也就提供了一些构造、拷贝构造、赋值运算符等一般功能,而最主要用来实现仿函数功能的operator()并没有出现,它是在基类functionN中实现的。

2. functionN模板

上面已经说的,特定function模板的偏特化是从对应(函数签名)的functionN继承的。这里同样使用function1模板来说明。

接下来列出function1在宏展开后的代码大概是(简便期间略去了访问限制和部分不重要的代码):

template<typename R, typename T0, typename Allocator = …>

class function1 : public function_base

{

typedef R result_type;

// 此函数类型对应的vtable类型,稍后解释

typedef boost::detail::function::basic_vtable1<R, T0, Allocator> vtable_type;

// 和前面的用途一样。后略

struct clear_type {};

// 构造函数

template<typename Functor>

function1(Functor const & f

,typename enable_if_c<

(boost::type_traits::ice_not<

(is_integral<Functor>::value)>::value),

int>::type = 0

) : function_base()

{

// 调用赋值函数

this->assign_to(f);

}

// 拷贝构造函数

function1(const function1& f) : function_base()

{

this->assign_to_own(f);

}

// 实现仿函数的关键,定义在类外部,非内联

result_type operator()(T0 a0) const;

/*省略赋值运算符和安全的bool转型*/

// 复制函数对象

void assign_to_own(const function1& f)

{

// 非空才赋值

if (!f.empty())

{

// 复制vtable的指针

this->vtable = f.vtable;

// 调用管理器的复制功能,后面解释

f.vtable->manager(f.functor, this->functor,

boost::detail::function::clone_functor_tag);

}

}

// 赋值函数

template<typename Functor>

void assign_to(const Functor& f)

{

using detail::function::vtable_base;

// 获取此函数的标记

typedef typename detail::function::get_function_tag<Functor>::type tag;

// 根据标记获取调用者

typedef detail::function::get_invoker1<tag> get_invoker;

// 获取对应的应用器

typedef typename get_invoker::

template apply<Functor, R, T0, Allocator> handler_type;

// 获取具体的调用者

typedef typename handler_type::invoker_type invoker_type;

// 获取管理器

typedef typename handler_type::manager_type manager_type;

// 生成vtable

// 这里使用的静态值,对于函数签名不同的函数,自然不会有问题

// 那么对于函数签名相同的函数,他们的管理器和调用器应该是相同的,

// 所以可以复用vtable

// 而且管理器和调用器只与Functor的类型相关,所以在编译时就确定了。

static const vtable_type stored_vtable =

{ { &manager_type::manage /*vtable.base*/ },

&invoker_type::invoke/*vatble.invoker*/ };

// 把函数首先赋值到vtable中,成功后才保存此vtable

if (stored_vtable.assign_to(f, functor)) vtable = &stored_vtable.base;

else vtable = 0;

}

}

// 下面是result_type operator()(T0 a0) const;的实现

template<typename R, typename T0, typename Allocator>

typename function1<R, T0, Allocator>::result_type

function1<R, T0, Allocator>::operator()(T0, a0) const

{

if (this->empty())

boost::throw_exception(bad_function_call());

// 通过调用器调用

return reinterpret_cast<const vtable_type*>(vtable)->invoker (this->functor, a0);

}

以上便是function1的大概代码,可以看到,他实现了仿函数最为关键的operator(把具体的调用代理到了invoker),也同样实现的赋值功能(代理到了vtable)。

然而上面两个的具体实现还是隐藏着,而且具体保存函数地址的结构也没有出现,这需要在往下看。注意到这个类从function_base继承,而在function_base的实现里可以看到这我们保存的具体数据。

在看function_base之前,需要先说明一下invoker和manager,在附录B中,可以看到basic_vtable1中保存了两个指针,分别是invoker函数和manager函数(其实是vtable_base对象,而vtable_base里面有一个manager函数的指针)。

3. invoker

boost通过这个来真正的调用函数,同时还完成了在赋值时类型检查的功能。

下面说明invoker的具体实现(拿函数指针的invoker来说明):

template<typename FunctionPtr, typename R, typename T0>

struct function_invoker1

{

static R invoke(function_buffer& function_ptr, T0 a0)

{

FunctionPtr f = reinterpret_cast<FunctionPtr>(function_ptr.func_ptr);

return f(a0);

}

};

可以看到这个模板除了接受函数参数的类型还接受了函数指针的类型。在调用的时候,通过把保存的函数指针强制转换为目标函数指针后,再执行调用。

当然,上面说了这里还能解决为function对象赋值(赋入函数)时的类型检查。因为这里的FunctionPtr在赋值时候就是要赋入的函数对象的类型,那么在执行调用时候f(a0);会检测类型是否符合(如果参数个数不正确时直接报错)。这个是把类型检查完全交给了C++底层来做了,这样的优点就是:对于函数A和B,A接受的参数都能隐式转换为B接受的参数,那么使用function对A包装的对象f,然后再想把B赋值给f时也能通过编译,完成调用。

上面是针对函数指针的invoker,其他类型的invoker略去。

4. manager

因为要包装函数,必须保存对应的函数地址或者仿函数对象,那么这个manager就是用来管理这些保存的数据。

对于这个,首先要说明他使用的两个数据结构(具体见附录):union function_buffer;和enum functor_manager_operation_type;

前者是保存了函数地址或仿函数对象,后者枚举了针对这些数据的操作类型。

在源码中,他实现了两种管理器:reference_manager和functor_manager。

在附录中有详细介绍。

5. vtable

根据2里面的介绍,vtable中保存了invoker函数和manager函数的指针,而每个function_base中保存了一个到vtable的指针。当然,除了维护这两个指针,它还提供了assign_to的接口,在function1::assign_to中可以看到,最后的赋值时代理到这个vtable:: assign_to的,因为vtable维护了manager指针,通过正确的manager来实现assign。具体见附录。

6. function_base

所以function对象的最底层基类,他主要维护了两个成员:function_buffer(保存了函数地址或者仿函数对象或其引用)和vtable指针(执行此类型对应的vtable)。

三、整理与总结

1、对于4种不同的Functor使用相同的数据结构(function_buffer)来保存。

2、为Functor定义4种操作类型,分别为4种Functor定义执行这4种操作的管理器。

3、分别为这四种Functor定义了调用器,用来调用Functor。

4、使用vtable的把每个函数类型对应的管理器和调用器组织在一起。这里的vtable可以理解为虚函数列表,因为保存的都是函数指针,虽然你这些都是在编译器确定的,但是都是根据不同的Functor类型动态确定的。

5、在function_base中维护了function_buffer和vtable。

6、functionN继承了function_base,他本身也是个模板,根据具体的函数实例化,并且提供了以下赋值(内部通过invoker实现了类型检查)、比较等函数,同时可以对他进行函数调用。

附录:

A 模板元函数(MetaFunction)

1、template<typename F> class get_function_tag;

template<typename F>

class get_function_tag

{

// 根据是否指针判断是指针还是对象

typedef typename mpl::if_c<(is_pointer<F>::value),

function_ptr_tag,

function_obj_tag>::type ptr_or_obj_tag;

// 判断是否是成员指针

typedef typename mpl::if_c<(is_member_pointer<F>::value),

member_ptr_tag,

ptr_or_obj_tag>::type ptr_or_obj_or_mem_tag;

// 是否是引用

typedef typename mpl::if_c<(is_reference_wrapper<F>::value),

function_obj_ref_tag,

ptr_or_obj_or_mem_tag>::type or_ref_tag;

public:

// 结果类型,为四种标记类型的一种

typedef or_ref_tag type;

};

用于计算传入的函数(Functor:可能是函数指针,成员函数,仿函数对象及其引用)对于的标记类型,用户后面的重载解析,使用不同的函数处理不同的类型。

下面个四种Functor类型对应的标记类型:

struct function_ptr_tag {}; // 一般的函数指针

struct function_obj_tag {}; // 仿函数对象

struct member_ptr_tag {}; // 成员函数指针

struct function_obj_ref_tag {}; // 仿函数对象的引用

2、template<typename F> struct function_allows_small_object_optimization;

template<typename F>

struct function_allows_small_object_optimization

{

// 下面的宏是用来定义类型中的静态常量

// 第一个参数是类型,第二个是”变量 = 值”

BOOST_STATIC_CONSTANT

(bool,

//F大小小于function_buffer,并且function_buffer的需求的对齐值是F的整数倍

value = ((sizeof(F) <= sizeof(function_buffer) && (alignment_of<function_buffer>::value % alignment_of<F>::value == 0))));

};

此元函数用来计算functor是否使用小对象优化(直接在function_buffer中存储,而不用再分配内存)

3、用于选择调用者的元函数

template<typename FunctionPtr, typename R

BOOST_FUNCTION_COMMA

BOOST_FUNCTION_TEMPLATE_PARMS>

// 其实是get_function_invokerN,

// 选择function_invokerN或者void_function_invokerN

struct BOOST_FUNCTION_GET_FUNCTION_INVOKER;

template<typename FunctionObj, typename R

BOOST_FUNCTION_COMMA

BOOST_FUNCTION_TEMPLATE_PARMS>

// 其实是get_function_obj_invokerN,

// 选择function_obj_invokerN或者void_function_obj_invokerN

struct BOOST_FUNCTION_GET_FUNCTION_OBJ_INVOKER;

template<typename FunctionObj, typename R

BOOST_FUNCTION_COMMA

BOOST_FUNCTION_TEMPLATE_PARMS>

// 其实是get_function_ref_invokerN,

// 选择function_ref_invokerN或者void_function_ref_invokerN

struct BOOST_FUNCTION_GET_FUNCTION_REF_INVOKER;

template<typename MemberPtr, typename R

BOOST_FUNCTION_COMMA

BOOST_FUNCTION_TEMPLATE_PARMS>

// 其实是get_member_invokerN,

// 选择member_invokerN或者void_member_invokerN

struct BOOST_FUNCTION_GET_MEMBER_INVOKER

他们通过判断返回类型R是不是void来选择不同的调用者。

4、template<typename Tag> struct BOOST_FUNCTION_GET_INVOKER { };

其实具体的结构名是get_invokerN,此模板为空,但是分别由针对四种Functor类型的特化。他们通过get_function_tag返回的标记来获取给定函数对象的调用者。每个特化包含一个apply的内部类模板,它接受函数对象,返回类型,函数参数类型,分配器。apply类包含两个typedef:invoker_type和manager_type,它们分别对应调用者和管理器的类型。

B 数据结构

1、union function_buffer;

union function_buffer

{

// 函数对象的指针

void* obj_ptr;

// std::type_info对象的指针(get_functor_type_tag和check_functor_type_tag使用)

const void* const_obj_ptr;

// 函数指针

mutable void (*func_ptr)();

// 包装成员指针

struct bound_memfunc_ptr_t

{

void (X::*memfunc_ptr)(int);

void* obj_ptr;

} bound_memfunc_ptr;

// 用来放松别名限制

mutable char data;

};

此联合是用来用于保存小的函数对象的缓冲。它是一个包含函数指针、对象指针、模仿成员函数指针结构的联合。

2、enum functor_manager_operation_type;

enum functor_manager_operation_type

{

clone_functor_tag, // 复制

destroy_functor_tag, // 销毁

check_functor_type_tag, // 检测两个Functor类型是否相同,比较两个的typeid, // 一样的话,则进行复制。

get_functor_type_tag // 获取Functor类型,把Functor的typeid存在上面联 // 合的const_obj_ptr中

};

此枚举用于描述在union function_buffer上执行的操作类型。

3、struct vtable_base;

struct vtable_base

{

void (*manager)(const function_buffer& in_buffer,

function_buffer& out_buffer,

functor_manager_operation_type op);

};

此结构用来保存一个管理器函数指针。

下面介绍管理器:

主要有两个管理器:

(1) template<typename F> struct reference_manager

主要处理引用类型Functor,仅复制指针而不分配空间,算是trivial的

(2) template<typename Functor, typename Allocator> struct functor_manager

处理其他三种Functor,主要有函数:

{

private:

// 处理函数指针类型,同样是进行指针的复制

static inline void

manager(const function_buffer& in_buffer, function_buffer& out_buffer,

functor_manager_operation_type op, function_ptr_tag);

// 处理函数对象funtion类型

// 处理小对象优化的情况,直接把结果存在联合内部

// 通过placement new实现,在销毁时直接调用functor的析构函数

static inline void

manager(const function_buffer& in_buffer, function_buffer& out_buffer,

functor_manager_operation_type op, mpl::true_);

// 处理函数对象funtion类型

// 这里是非小对象的情况

// 需要在外部分配空间,function_buffer中只保存指针。

// 可以使用外部提供的分配器来分配内存。

static inline void

manager(const function_buffer& in_buffer, function_buffer& out_buffer,

functor_manager_operation_type op, mpl::false_);

// 对于函数对象,我们要确定此函数对象是使用小对象优化

// 还是需要在堆上分配空间

static inline void

manager(const function_buffer& in_buffer, function_buffer& out_buffer,

functor_manager_operation_type op, function_obj_tag);

// 对于成员指针,我按照小对象的方式处理他们

static inline void

manager(const function_buffer& in_buffer, function_buffer& out_buffer,

functor_manager_operation_type op, member_ptr_tag);

public:

// 提供为外界的唯一接口,处理三种Functor类型

static inline void

manage(const function_buffer& in_buffer, function_buffer& out_buffer,

functor_manager_operation_type op);

}

4、class function_base;

此类是boost::function的基础类,包含了它们的大部分通用功能。

它主要维护两个数据成员:

// 实际上是执行basic_vtableN的指

// 针,basic_vtableN除了维护了一

// 个vtable_base对象,还维护了一

// 个调用者函数的指针(后面介绍)

const detail::function::vtable_base* vtable;

mutable detail::function::function_buffer functor; // 实际上保存Functor的缓冲

它对外提供的接口有:

// 检测是否为空

bool empty() const { return !vtable; }

// 获取Functor的typeid,通过管理器实现vtable->manager(…)

const BOOST_FUNCTION_STD_NS::type_info& target_type() const;

// 获取目标的functor指针,

// 通过管理器vtable->manager(…, detail::function::check_functor_type_tag)

// 并指定操作是check_functor_type_tag,把类型相同的Functor复制出来

template<typename Functor> Functor* target()

template<typename Functor> const Functor* target() const

// 是否包含指定的Functor,通过比较target和传入的f是否相等

template<typename F> bool contains(const F& f) const

5、template<typename R

BOOST_FUNCTION_COMMA BOOST_FUNCTION_TEMPLATE_PARMS,

typename Allocator>

// 其实是basic_vtableN

struct BOOST_FUNCTION_VTABLE;

此结构主要维护了两个成员:

vtable_base base;

typedef result_type (*invoker_type)(function_buffer&

BOOST_FUNCTION_COMMA

BOOST_FUNCTION_TEMPLATE_ARGS);

invoker_type invoker;

主要接口有:

public:

// 此函数根据F的标记类型(使用get_function_tag原函数获得)分别调用下面4个

// 私有的assign_to中的一个

template<typename F> bool assign_to(const F& f, function_buffer& functor) const;

void clear(function_buffer& functor) const;

private:

template<typename FunctionPtr>

bool assign_to(FunctionPtr f, function_buffer& functor, function_ptr_tag) const;

// 成员指针,直接可以使用小对象优化

template<typename MemberPtr>

bool assign_to(MemberPtr f, function_buffer& functor, member_ptr_tag) const;

// 使用元函数function_allows_small_object_optimization来判断能否使用小对象优化

// 分别调用对应的assign_functor重载

template<typename FunctionObj>

bool assign_to(const FunctionObj& f, function_buffer& functor, function_obj_tag) const;

template<typename FunctionObj>

bool assign_to(const reference_wrapper<FunctionObj>& f,

function_buffer& functor, function_obj_ref_tag) const;

// 可以使用小对象优化,

template<typename FunctionObj>

void assign_functor(const FunctionObj& f, function_buffer& functor, mpl::true_) const;

// 不能使用小对象优化,需要在堆上分配内存

template<typename FunctionObj>

void assign_functor(const FunctionObj& f, function_buffer& functor, mpl::false_) const;

6、template<typename R

BOOST_FUNCTION_COMMA

BOOST_FUNCTION_TEMPLATE_PARMS,

typename Allocator = BOOST_FUNCTION_DEFAULT_ALLOCATOR>

// 其实就是functionN,是boost::function的具体实现类

// 从function_base继承,

// 如果参数是1或2时,还从std的unary_function或者binary_function继承

class BOOST_FUNCTION_FUNCTION : public function_base

它自身不单独维护数据,所有的数据都保存在function_base内了。

提供的接口有:

public:

// 定义对应的basic_vtableN类型

typedef boost::detail::function::BOOST_FUNCTION_VTABLE<

R BOOST_FUNCTION_COMMA BOOST_FUNCTION_TEMPLATE_ARGS, Allocator>

vtable_type;

// 函数调用

// 使用basic_vtableN的invoker来执行调用

result_type operator()(BOOST_FUNCTION_PARMS) const;

// 赋值运算符

BOOST_FUNCTION_FUNCTION&

operator=(Functor BOOST_FUNCTION_TARGET_FIX(const &) f);

BOOST_FUNCTION_FUNCTION& operator=(int zero);

// 拷贝复制运算符

BOOST_FUNCTION_FUNCTION& operator=(const BOOST_FUNCTION_FUNCTION& f);

// 交换

void swap(BOOST_FUNCTION_FUNCTION& other);

// 清空

void clear();

// bool转型的惯用

operator safe_bool () const { return (this->empty())? 0 : &dummy::nonnull; }

private:

// 复制一个函数对象,直接复制vtable指针,并调用管理器的复制功能

void assign_to_own(const BOOST_FUNCTION_FUNCTION& f);

// 赋入一个Functor,

// 首先根据Functor对应的标记类型获取适当的管理器和调用器,使用这俩个的指针

// 建立一个basic_vtableN对象(boost中是使用静态变量,对于函数签名不同的函数,

// 自然不会有问题那么对于函数签名相同的函数,他们的管理器和调用器应该是相同

// 的,所以可以复用vtable)

template<typename Functor> void assign_to(const Functor& f)

7、function模板

继承自functionN,它提供了更加友好的接口(仅仅提供了函数签名的特化)。如声明function<int (int)>,而如果使用function1的话,只能声明为function1<int, int>。对于成员函数,声明如下:function<int (X*, int)>,否则的话function2<int, X*, int>。

March 06

Data Alignment in VC++(x86)

C++中,struct和class的大小(sizeof操作符)很大程度上依赖编译器的数据对齐(Data Alignment)设置。下面是一些总结(在x86机器上使用VC++):

一、计算类型的需求对齐值(Alignment-Requirement)

1、开始

首先在VC++中,每个基本类型都有它自己需求的对齐值(Alignment-Requirement)。可以使用__alignof()操作符来获取。如:

Expression

Value

__alignof( char )

1

__alignof( short )

2

__alignof( int )

4

__alignof( __int64 )

8

__alignof( float )

4

__alignof( double )

8

__alignof( char* )

4

可见这些值与对应类型所占的字节数是相同的,而且这些值是不变的。

2、仅使用#pragma pack指定对齐值

当这些类型包装在一个结构中时,如:

typedef struct { int a; double b; } S; // 默认情况下 A = __alignof(S) == 8

这时就有考虑包装数据时的对齐设置(通过#pragma pack指定),这个值默认是8。

如果设对齐设置值为P,要包装的结构中成员的最大需求对齐值为RM(不考虑存在__declspec(align(X))的情况,)那么A = __alignof(S) == min(P,RM)

所以有:

#pragma pack(push, 4) //设置当前对齐值为4

typedef struct { int a; double b; } S; // A = __alignof(S) == 4

#pragma pack(pop) //restore

3、存在__declspec(align(X))指定对齐值

首先为每个自定义类型定义一个内部属性(Attribute):bool hasSpecAlign,默认为false。

当在以下情况下是,此属性变为true:

I. 定义此类型时,使用了__declspec(align(X)),设值为XD

II. 定义类型内部成员时,使用了__declspec(align(X)),设最大值为XI

III. 类型内部某个成员是自定义类型,并且此类型的该属性值为true。设所有为true的类型的需求的最大对齐值为XT

那么,对于存在__declspec(align(X))指定对齐值的情况,类型的需求对齐值为AFinal = max(A, XD, XI, XT)

例如:

typedef __declspec(align(16)) struct { int a; double b; } S1; // __alignof(S1) == 16 sizeof(S1) == 16

typedef struct {__declspec(align(32)) int a; double b; } S2; // __alignof(S2) == 32 sizeof(S2) == 32

// 要求a的偏移为32的整数倍,0满足条件

typedef struct {int a; __declspec(align(32)) double b; } S3; //__alignof(S3) == 32 sizeof(S3) == 64

// 要求b的偏移为32的整数倍,并且预留X整数倍的空间,都取1即满足条件

typedef struct {int a; __declspec(align(16)) int b; } S4; // __alignof(S4) ==16 sizeof(S4) == 32

typedef struct {int a; S4 b; } S5; //__alignof(S5) == 16 sizeof(S5) == 48

二、包装数据

1、仅使用#pragma pack指定对齐值

通过#pragma pack指定包装数据时使用的对齐值P,这个设置将影响用户自定义类型的Alignment-Requirement(上面已经解释)。

包装数据时对齐的原则:假定通过#pragma pack指定包装数据时使用的对齐值为P,当前数据类型的Alignment-Requirement为A,那么在包装数据时,取AFinal=min(A, P)来执行对齐,最后结构或类的大小必须是AFinal的最小整数倍。例如:

struct S1

{

int a;

double b;

char c;

};

// 当前是默认设置,即P == 8

// 对于a,int的需求是4,对齐值取4,偏移则为4的整数倍,0满足条件

// 对于b,double的需求是8,对齐值取8,偏移则为8的整数倍,8满足条件

// 对于c,char的需求值是1,对齐值取1,偏移则为1的整数倍,16满足条件

// 最后sizeof(S1) == 24

// S1的内存布局:11110000 11111111 10000000


#pragma pack(push, 4)

struct S1

{

int a;

double b;

char c;

};

// P == 4

// 对于a,int的需求是4,对齐值取4,偏移则为4的整数倍,0满足条件

// 对于b,double的需求是8,对齐值取4,偏移则为4的整数倍,4满足条件

// 对于c,char的需求值是1,对齐值取1,偏移则为1的整数倍,8满足条件

// 最后sizeof(S1) == 16

// S1的内存布局:11111111 11111000

2、存在__declspec(align(X))指定对齐值

这里需要考虑前面定义的那个类型属性:hasSpecAlign。当然也要分情况讨论:

1) 仅在定义类型的前面使用了__declspec(align(X)),那么这个设置对于类型的布局原则没有影响(也就是还安装前一节的方法),这个设置仅仅影响了类型的sizeof值,必须为X的整数倍,不够的话需要填充。

2) 存在某成员使用__declspec(align(X))指定对齐值的情况,表示此成员在此结构中的偏移必须为X的整数倍,并且预留X整数倍的空间。这里只是预留空间,后面的成员仍可按照它自己的对齐值来包装,可以使用预留的内存。例如:

typedef struct {int a; __declspec(align(16)) char b; __declspec(align(32)) double c; short d;} S

// __alignof(S) ==32 sizeof(S) == 64

// 内存布局为 11110000 00000000 10000000 00000000

//               11111111 11110000 00000000 00000000

3) 存在某个成员是自定义类型,并且此类型的hasSpecAlign属性为true,那么包装时按照2)的方法。例如:

typedef struct {int a; __declspec(align(16)) int b; } S4; // __alignof(S4) ==16 sizeof(S4) == 32

typedef struct {int a; S4 b; } S5; //__alignof(S5) == 16 sizeof(S5) == 48

//内存布局为 11110000 00000000                            这里是S5.a

//              11110000 00000000 11110000 00000000 这里是S5.b

三、语法

1、__declspec(align(X))

X必须为0~8192中2的幂值。

可以在结构声明前使用,也可以在成员变量定义前使用,但是不能在参数前面使用。

2、#pragma pack()

语法:#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n)

#pragma pack(show) 在TRACE窗口输出当前使用的pack值

push 把n压入pack栈并且使用指定的n为pack值,如果不指定n,将把当前值入栈

pop 删除pack栈栈顶的元素。如果没有指定n,那么会使用新的栈顶作为pack值;如果知道了n,如#pragma pack(pop, 16),那么会使用16作为当前的pack值(不压栈);

n 默认为8,可以为1, 2, 4, 8, 16。