Month: Thursday May 10th, 2012

bind2nd普通二元函数时无法使用引用类型参数的问题

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/cplusplus-bind2nd-reference/ 在使用 STL 的 find_if、count_if 等函数时会发现这些函数使用的参数不是普通的函数指针,而是 functional 函数对象,实际使用函数对象时可以自己定义一个仿函数来实现(实现带参数的 operator () 即可),这个相对比较简单就不写出来了。但有些情况需要直接使用普通的二元函数指针,这时可以使用 ptr_fun 将函数指针转换为函数对象作为 find_if、count_if 等的参数。 先看一个能正常工作的二元函数不使用引用类型参数的代码: 程序很简单,从 vector 中查找符合条件的 sss 对象,find_sss 就是要转换的二元函数指针,第一个参数是 sss 类,通过 ptr_fun 可以使本代码正常工作。 通过下面的运行输出能看出调用 find_sss 时进行了拷贝构造(本程序的编译环境为:Windows 7 32bit, Mingw gcc 3.4.5,Visual Studio 2010中稍有不同,主要在前面的拷贝次数上): ---copy 0x22ff10 to 0x552a58 ---copy 0x552a58 to 0x552ad8 ---copy 0x22ff10 to 0x552ae0 ---copy 0x552ad8 to 0x552af0 ---copy 0x552ae0 to 0x552af8 ---copy 0x22ff10 to 0x552b00 ---copy 0x22ff10 to 0x552b08 ---copy 0x552af0 to 0x552b18 ---copy 0x552af8 to 0x552b20 ---copy 0x552b00 to 0x552b28 ---copy 0x552b08 to 0x552b30 ---copy 0x22ff10 to 0x552b38 before find_if ---copy 0x552b18 to 0x22fdd0 ---copy 0x22fdd0 to 0x22fd40 ---copy 0x552b20 to 0x22fdd0 ---copy 0x22fdd0 to 0x22fd40 ---copy 0x552b28 to 0x22fdd0 ---copy 0x22fdd0 to 0x22fd40 index: 2, value: 13 接下来就是实际碰到的问题了,如果将 find_sss 的第一个参数改为 sss 的引用,即第 24 行改为: bool find_sss(sss& s_chk, int val) 上面的代码就会编译出错(以 Visual Studio 2010 的错误输出为例): C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(341) : error C2535: “bool std::binder2nd<_Fn2>::operator ()(sss &) const”: 已经定义或声明成员函数 with [ _Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)> ] C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\xfunctional(335) : 参见“std::binder2nd<_Fn2>::operator ()”的声明 with [ _Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)> ] test.cpp(43): 参见对正在编译的类 模板 实例化“std::binder2nd<_Fn2>”的引用 with [ _Fn2=std::pointer_to_binary_function<sss &,int,bool,bool (__cdecl *)(sss &,int)> ] 网上的码农和攻城师们基本都认为是 STL 本身的问题,传递引用类型参数会造成 reference to reference 问题。 几番尝试之后,发现的解决方法如下: 1、Visual Studio 2010下的不完美解决方法: 将第 43 行改为:     iii = find_if(vvv.begin(), vvv.end(), bind2nd(pointer_to_binary_function<sss, int, bool, bool(*)(sss&, int)>(find_sss), 13)); 这样通过自己给 pointer_to_binary_function 设置模板参数避免编译出错,实际运行中会发现 find_if 查找可以正常工作了,但第 27 行中修改引用类的值会没有效果,因为这种方式不是真正的引用,仍然有拷贝构造,由于需要用到这些二元函数的场合一般不需要修改数据,使用起来没有太大问题。 2、gcc下的不完美解决方法: 如果 gcc 下用上面的改动,你会发现由于 gcc 下 pointer_to_binary_function 只有 3 个参数,无法顺利修改编译,但可以这样折中,将第 43 行改为:     iii = find_if(vvv.begin(), vvv.end(), bind2nd(ptr_fun((bool(*)(sss, int)) find_sss), 13)); 通过强制类型转换来实现,稍显恶心,查找可以正常工作,但依然是拷贝构造。需要注意此方法如果在 Visual Studio 中使用会出错。 3、终极解决方案- 使用 boost 库: 首先头文件中增加: #include <boost/functional.hpp> 然后原来的第 43 行改为:     iii = find_if(vvv.begin(), vvv.end(), boost::bind2nd(boost::ptr_fun(find_sss), 13)); 编译运行之后发现 find_if 查找可以正常工作,而且现在是真正的引用,只能说 boost 的 functional 相比 STL 的实在是好强大,哈哈。 运行输出如下(使用 Mingw gcc 3.4.5 编译),find_if 找到的值已经被 find_sss 修改: ---copy 0x22ff10 to 0x7f2a58 ---copy 0x7f2a58 to 0x7f2ad8 ---copy 0x22ff10 to 0x7f2ae0 ---copy 0x7f2ad8 to 0x7f2af0 ---copy 0x7f2ae0 to 0x7f2af8 ---copy 0x22ff10 to 0x7f2b00 ---copy 0x22ff10 to 0x7f2b08 […]

C++虚基类的实现验证

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/cplusplus-virtual-base-class/ 在C++的某些应用环境中,如果有多个派生类同时继承同一个基类,但只要想基类成员的一份拷贝,这样可以把对基类的继承声明为虚拟(virtual)的,以达到需要的目的,用 virtual 限定的继承即为虚继承,对应的基类即为虚基类,没有则为非虚基类。在派生类产生的对象中,同名的虚基类只产生一个虚基类对象,非虚基类则有各自的对象,需要注意虚基类和抽象类的区分。 另外特别需要注意的是一个基类即可以被用作虚基类,也可以被用作非虚基类,这纯粹看是否是虚继承(是否有 virtual 限定)。虚基类子对象是由最远派生类(也就是建立对象时所指定的类)的构造函数调用虚基类的构造函数进行初始化。如果一个派生类继承了多个基类,其中既有虚基类,也有非虚基类,则虚基类构造函数优先于非虚基类的构造函数,而不管实际书写顺序,而且如果该派生类的构造函数初始化列表中同时有对虚基类和非虚基类构造函数的调用,同样也是虚基类优先。 有关虚基类的其它说明请参考其它书籍咯,下面是测试的代码: 比较简单,Base2 和 Base3 分别派生自 Base,而且都是虚继承,Base4 同时派生自 Base2 和 Base3,但 Base3 前有 virtual,表示 Base4 对 Base3 是虚继承,Base4 对 Base2 是非虚继承。另外代码中比较特殊的一点是 Base 基类中去掉了默认构造函数,改为一个 int 参数的构造函数(第 8 行),因此 Base2 和 Base3 的构造函数的初始化列表中都需要增加对基类 Base 构造函数的调用,同样 Base4 需要调用 Base、Base2 和 Base3。 主程序中只是生成一个 Base4 的对象,上述代码的运行输出为: init Base 0x22ff34, value:22 init Base3 init Base2 destory Base 0x22ff34, value:22 由上面的输出可以验证虚基类的概念: 由于 Base2 和 Base3 都是对 Base 的虚继承,虽然 Base4 中分别调用了 Base2 和 Base3 的构造函数,但虚继承就表示基类 Base 只有一份拷贝,Base 只需要构造一次。另外由于 Base4 对 Base2 是非虚继承,对 Base3 是虚继承,尽管 Base2 看起来在前面调用,但由于 Base3 对 Base4 是虚基类,它的构造函数就需要先于 Base2 对调用,因此 ”init Base3“ 在 ”init Base2“ 前面。 接着对代码稍作改动,将 Base2 和 Base3 都改为非虚继承,Base4 中去掉对 Base 基类构造函数的调用: 第22行:class Base2 : public Base 第31行:class Base3 : public Base 第43行:    Base4(int val) : Base2(val), Base3(val) 编译运行输出,结果为: init Base 0x22ff38, value:22 init Base3 init Base 0x22ff34, value:22 init Base2 destory Base 0x22ff34, value:22 destory Base 0x22ff38, value:22 可以看到由于 Base2 和 Base3 都不是非虚继承,Base 就被构造了两次。 在上面的基础上再做个小改动,把 Base4 对 Base3 的派生也改为非虚继承: 第40行:class Base4 : public Base2, public Base3 再次运行输出,结果即为: init Base 0x22ff30, value:22 init Base2 init Base 0x22ff34, value:22 init Base3 destory Base 0x22ff34, value:22 destory Base 0x22ff30, value:22 由于 Base4 对 Base2 和 Base3 都不是虚继承了,对 Base2 和 Base3 的构造函数调用就按实际顺序了。 这样虚基类的概念就算是比较清楚咯。 ^_^

vector的push_back拷贝构造和空间占用分析

本文同步自:https://zohead.com/archives/vector-push-back-space-copy/ 这两天在实际程序中使用 STL 的 vector push_back 类对象时出现问题,偶尔发现 vector 在 push_back 时的调用类对象的拷贝构造函数和析构函数有点特别,简单做下分析。 程序代码: 功能很简单,main 中定义一个 sss 类对象和对应的 vector,然后在循环中改类成员的值,并依次 push_back 到 vector 中,类的构造函数、析构函数、拷贝构造函数中都加了对应的打印输出。循环运行了5次,往 vector 中增加了5个类成员。 实际运行输出如下: ---init sss 0x22ff20, value:11 ---copy 0x22ff20 to 0x5d2a58 size: 1, capacity: 1 ---copy 0x5d2a58 to 0x5d2ad8 ---copy 0x22ff20 to 0x5d2adc ---destory sss 0x5d2a58, value:12 size: 2, capacity: 2 ---copy 0x5d2ad8 to 0x5d2ae8 ---copy 0x5d2adc to 0x5d2aec ---copy 0x22ff20 to 0x5d2af0 ---destory sss 0x5d2ad8, value:12 ---destory sss 0x5d2adc, value:13 size: 3, capacity: 4 ---copy 0x22ff20 to 0x5d2af4 size: 4, capacity: 4 ---copy 0x5d2ae8 to 0x5d2b00 ---copy 0x5d2aec to 0x5d2b04 ---copy 0x5d2af0 to 0x5d2b08 ---copy 0x5d2af4 to 0x5d2b0c ---copy 0x22ff20 to 0x5d2b10 ---destory sss 0x5d2ae8, value:12 ---destory sss 0x5d2aec, value:13 ---destory sss 0x5d2af0, value:14 ---destory sss 0x5d2af4, value:15 size: 5, capacity: 8 ---destory sss 0x5d2b00, value:12 ---destory sss 0x5d2b04, value:13 ---destory sss 0x5d2b08, value:14 ---destory sss 0x5d2b0c, value:15 ---destory sss 0x5d2b10, value:16 ---destory sss 0x22ff20, value:16 结果分析: vector 每次调用 push_back 时都会拷贝一个新的参数指定的 sss 类对象,这会调用 sss 的拷贝构造函数,第一次的 copy 正常,而且 vector 的实际容量也由 0  变为 1。 第二次调用 push_back,通过输出会发现调用了两次拷贝构造函数,一次析构函数,原来 vector 此时判断容量不够,将容量扩大为原来的两倍,变为 2,并将原来的元素再次拷贝一份存放到新的内存空间,然后拷贝新加的类对象,最后再释放原来的元素。 第三次调用 push_back 时,vector 自动扩大为4,因此拷贝构造函数调用了3次,析构函数调用了2次,程序最终退出了时就析构了 5 次加本身的 sss 类对象一共 6 次。 参考: 由此看来,vector 的 push_back 在发现空间不足时自动将空间以 2 的指数增长:0 -> 1 -> 2 -> 4 -> 8 -> 16 -> 32 … 查找资料后得知,如此设计的主要目的是为了尽可能的减小时间复杂度;如果每次都按实际的大小来增加 vector 的空间,会造成时间复杂度很高,降低 push_back 的速度。 另外关于 push_back 为什么会执行拷贝构造函数,push_back 的原型为: void push_back(const _Ty& _Val) 参数是以引用方式传递,按说不会拷贝,但 push_back 实际实现中判断空间不足时是调用 insert 函数添加元素: void push_back(const _Ty& _Val) {     // insert element at end     if (size() < capacity())     #if _HAS_ITERATOR_DEBUGGING     {         // room at end, construct it there         _Orphan_range(_Mylast, _Mylast);         _Mylast = _Ufill(_Mylast, 1, _Val);     }     #else /* _HAS_ITERATOR_DEBUGGING */         _Mylast = _Ufill(_Mylast, 1, _Val);     #endif /* _HAS_ITERATOR_DEBUGGING */     else         insert(end(), _Val); } 更新: 2012-05-10:       近期在 Visual Studio 2010 中发现 vector 的实际空间增加顺序为:1 - 2 - 3 - 4 […]

解决socket在execl之后被自动继承的问题

本文同步自:https://zohead.com/archives/close-socket-after-execl/ Linux下的进程在创建子进程(fork)后,子进程会自动继承父进程的文件描述符,这种机制在没有 execl 运行时一般都没有问题,而且也比较方便在子进程中直接使用父进程中打开的文件描述符,但如果子进程通过 execl 类函数运行新的程序时,这些继承的文件描述符却没有被关闭,导致 execl 之后的程序中也有父进程中文件描述符的拷贝,有些情况就会出现错误。 由于 socket 也在这些文件描述符范围内,本文简单说下解决 socket 被自动继承的几种方法。 首先是测试程序,头文件就没列出来了。 父进程和子进程分别 sleep 10秒钟,方便外部查看打开的文件描述符,编译运行程序,使用 lsof 命令就可以看到子进程在 execl 之后的 sleep 进程中也打开了父进程的 socket 描述符(UDP连接),这不是我们想要的效果滴: [root@localhost ~]# lsof -c sleep COMMAND     PID    USER   FD        TYPE  DEVICE SIZE/OFF   NODE      NAME sleep               5750  root       cwd      DIR     8,1           4096           8145         /root sleep               5750  root      rtd         DIR     8,1           4096           2                / sleep               5750  root      txt         REG    8,1           27880         456104    /bin/sleep sleep               5750  root     mem    REG    8,1           155696       301801    /lib64/ld-2.12.so sleep               5750  root     mem    REG    8,1           1912928    301802    /lib64/libc-2.12.so sleep               5750  root     mem    REG    8,1           99158752  391655   /usr/lib/locale/locale-archive sleep               5750  root     0u        CHR    136,1      0t0                4              /dev/pts/1 sleep               5750  root     1u        CHR    136,1      0t0               4               /dev/pts/1 sleep               5750  root     2u        CHR    136,1      0t0               4               /dev/pts/1 sleep               5750  root     3u        IPv4    15022      0t0             UDP         *:5555 解决办法有如下几种: 1、比较死板的方法,在子进程 fork 之后关闭所有的 socket 描述符,在 execl 之前加上:     int fd = 0, fdtablesize = 0;     for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++) {         // close all socket handle inherited from parent process         if (isfdtype(fd, S_IFSOCK)) {             close(fd);         }     }     这种处理比较彻底,即使子进程没有 execl 也会关闭继承的 socket 句柄,如果要关闭从父进程继承的所有句柄,去掉 isfdtype 判断即可。 2、使用 fcntl 为父进程 socket 设置 O_CLOEXEC 标志,表示此 socket 在 execl 之前自动关闭(注意并非在 fork 之后),在 socket 创建完成之后加上:     ret = fcntl(sock, F_GETFD);     ret = fcntl(sock, F_SETFD, ret | FD_CLOEXEC); 3、Linux 2.6.27 之后创建 socket 时开始支持 SOCK_CLOEXEC 参数,与上面的效果相似,只要改创建 socket 的地方:     sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);     另外 Linux 2.6.27 之后创建 socket 另外支持 SOCK_NONBLOCK 参数,如果设置此参数,socket 自动创建为非阻塞式。 以上三种办法均在 RHEL 6.1 64位系统上编译测试通过,有任何问题欢迎交流,玩的开心 ^_^