Your Ad Here
首页 | 编程语言 | 网站建设 | 游戏天堂 | 冲浪宝典 | 网络安全 | 操作系统 | 软件时空 | 硬件指南 | 病毒相关 | IT 认证
软讯网络 > 操作系统 > Linux > C++中指向成员函数的
【标  题】:C++中指向成员函数的
【关键字】:C++
【来  源】:http://www.cublog.cn/u/8681/showart.php?id=252215

C++中指向成员函数的

Your Ad Here

C++中指向成员函数的"指针"


在{C++ Common knowledge}的Item 16的标题明确指出: 指向类的成员函数的指针并非指针, 作者的意思是说指向类的成员函数的指针并非象C语言中的函数指针一样本身就是一个地址. 该文对指向函数的指针给出了忠告:指向成员函数的指针必需存储一些信息, 以指明:

  • 它所指向的函数是虚拟的还是非虚拟的(并非必需, 在下面GCC的实例中并没有存储这个信息)
  • 到哪里去找适当的虚函数表指针(真正必需的)
  • 从函数的this指针加上或减去一个偏移量
  • 以及其它一些信息{打哈哈的态度, 牛人也有流俗的时候}
另外补充说: 指向成员函数的指针通常实现为一个小型的结构, 里面存放着这些信息.

这是这一条款中最详尽深入的说明了, 没有再进一步的了, 不够解渴, 具体的机制是什么, 典型的实现呢, 运行时的开销有多大? 下面以C/C++本身的代码来展示GCC中类的成员函数的典型实现

显然, 对于单个的类, 没有virtual函数的情形时是最简单的, 此时成员函数的指针完全可以实现为一个普通的C函数指针:

#include "stdio.h"     //HTML会吃掉<, 所以干脆用""
class A {
public:
void foo() { puts("I'm foo()") ; }
};
int main()
{
typedef void (*global_fp)(void *);
union {
void (A::*fp) ();
global_fp what;
} unknown = { &A::foo };
printf("sizeof(pointer to member function):%d\n", sizeof(unknown.fp) );
printf("function pointer: %#x\n", unknown.what);
A a;
(a.*unknown.fp)(); //最规矩的用法, 第一对括号是必要的, 否则fp会先与()结合
unknown.what( &a ); //通过一般的函数指针调用, 因为该成员函数并没有通过
//this指针访问任何其它成员, 所以unknown.what(0) 也可以
return 0;
}
当类之间有了继承关系, 基类的成员函数指针赋值给了派生类的函数指针时, 再加上虚函数, 实现没有那么简单了:
#include "stdio.h"

class A{ public:
virtual void bar() { puts("I'm bar()" ); } //故意让它是virtual的
virtual void foo() { puts("I'm foo()" ); }
};
class B{ public: virtual void b_foo(){} }; //多出一个virtual只是为了让C对象的布局中B这个子
//对象是第一个
class C: public B, public A { };
int main()
{
typedef void (*global_fp)(void *);
union {
void (C::*fp) ();
struct {
int fp;
int delta;
}mem_fp; //这个结构是从GCC的 -g -Wa,-ahls=test.s -fverbose-asm 输出中得到的
//此时的C::*fp是本身是一个结构了, 其中fp其实是该函数在虚函数表中以字节为
//单位的偏移量+1, 而delta则是指对象A在对象C中的偏移量, 如此才能得到对象A
//的vptr. 这个偏移量是根据下面赋值的右值&A::bar计算来的
global_fp what;
} unknown = { &A::bar }; //注意A类的成员函数指针赋值给了C类中的函数指针变量
C a;
// 模拟编译器获取基类对象的vptr的方法. &a取得C对象的真正的地址,
// 把它看成简单的整数, 加上A基类对象在它里面的偏移量, 得到的是
// 基类对象的地址, 只是碰巧, GCC的实现里基类A对象的vptr指针地址
// 就是对象本身的地址, 也就是说它是基类对象A里的第一个物件,
// 而且地址也码的整整齐齐
void * vptr = (void*) ( (int)&a + unknown.mem_fp.delta );
// 一次间接引用, 这才得到存放虚函数指针的表的首地址
void * vtbl = * (void **)vptr;
// 看到得到的值, 虚函数表对一个类只有一份, 由编译器在编译期放在数据区,
// 所以它的地址应该跟一个静态的数据对象比较接近, 验证一下:
static int g_i = 10; //赋值10可以避免它被放在BSS区
printf("vtbl = %p, data: %p, __fp = %d, delta=%d\n",
vtbl, &g_i, unknown.mem_fp.fp, unknown.mem_fp.delta);
// 得到指向虚成员函数的指针, 这个多出来的1就是上面提到的Item 16中
// 作者声称的为了让0来表示一个空的成员函数指针.
global_fp g_fp = (global_fp) * (void**) ( (int)vtbl + (unknown.mem_fp.fp - 1) ;
// 看看得到的普通函数指针
printf("g_fp = %#x\n", g_fp) ;
(a.*unknown.fp) () ; // 常规的调用
g_fp( 0 ); // 费半天劲才搞定的调用, 由此可以规规矩矩照语言规范
// 的套路写代码才是正道

// 验证一下对象C中两个基类对象的布局.
A * xa = &a;
B * xb = &a;
printf("a = %#x, b = %#x, c= %#x\n", xa, xb, &a );
return 0;
}

但 是, 要先祷告一番不要在任何实用的代码里面写这种依赖于具体实现的code, 这是GCC 3.2 20020903 的实现, 不一定是其它编译器的做法, 这么做只是让自己对整个的解析过程有一个了解, 可以认为, 通过成员函数指针的调用有一个小小的代价, 也可以相当然其它编译器不会偏离这个性能指标太远, 否则太丢人了.

上面的代码看起来是顺利, 其实那些巧妙的union构造是通过查看汇编输出加上一些猜测尝试出来的, 这里只是结果, 琐碎的过程就不足道了

sed tricks and pitfalls:【上一篇】
linux如何备份整个硬盘:【下一篇】
【相关文章】
  • 学习C++的第二个练习题
  • Refactor!™ for C++
  • 学习C++的第一个练习题
  • 深入探讨C++中的引用
  • windows c++程序员开始用linux编程(一)编译程序
  • windows c++程序员开始用linux编程(二)创建新进程
  • MUI C++ Socket Library
  • 仿基因编程的C++源码
  • (GNU)C++调用C写的动态库
  • 经典的C++类型转换详解
  • 【随机文章】
  • 浅析MVC框架中View层的优雅设计及实例
  • Practical UNIX and Network Security Training Note
  • 《MS SQL Server 2000管理员手册》系列——1. Microsoft SQL Server 2000 概观
  • 网络包过滤的实现
  • BI相关的开源工具(转)
  • 如何同时启动多个Tomcat服务器
  • 总体了解C#(12.接口)
  • 批处理的介绍
  • 在虚拟主机上用asp.net轻松实现urlrewrite
  • 监控代码灰鸽子之配套三方组件包
  • 【相关评论】
    没有相关评论
    【发表评论】
    姓名:
    邮件:
    随机码*
    评论*
          
    |  首 页  |  版权声明  |  联系我们   |  网站地图  |
    CopyRight © 2004-2007 软讯网络 All Rigths Reserved.