Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2315202
  • 博文数量: 527
  • 博客积分: 10343
  • 博客等级: 上将
  • 技术积分: 5565
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-26 23:05
文章分类

全部博文(527)

文章存档

2014年(4)

2012年(13)

2011年(19)

2010年(91)

2009年(136)

2008年(142)

2007年(80)

2006年(29)

2005年(13)

我的朋友

分类: C/C++

2009-08-18 10:43:10

不论是指向数据成员, 还是成员函数, 本质都不是指针.
出现C++中指向类成员的指针之前, 指针一词是地址的等义语, 但指向类成员的指针并非地址. 指向数据成员的指针与指向成员函数的指针还需分别对待, 它们的实现往往并不相同.

非地址的本质上与下面取一个指向数据成员的地址语法是一致的:

int C::*p_ = &C::x_;

注意C是类名字, 而x_是其成员, 并不使用
C c;
&c.x_; 这样的语法, 这样取得的仍是普通的指向int的真正的指针. 所以取类成员的指针语法根本与该类的具体实例无关, 自然它不是地址.

但它又与计算该成员的最终地址相关, 问题在于, 对于数据成员, 在程序中容易取得的是其宿主对象本身的地址(this), 通过数据成员的指针(往往实现为一个相对类起始地址的偏移)就可以得到最终指向成员数据的地址.

在C++ Common knowledge(Item 15)中, 看到实际上指向数据成员的指针的实现往往是偏移值加1, 目的是让0可以表示一个类似空指针的概念. 而在另一条款中, 编译期的常量0可以隐式转换为一个指向类成员的指针, 包括数据成员和成员函数.

另外, 上述条款15中, 提到指向基类数据成员的指针可以转换为指向派生类成员的指针, 没提及的一点是, 这种隐式转换并非仅仅是语法上允许通过而已, 而是还要进行派生类与该基类之间对象的偏移的判断, 下面的代码可以看出这一点:
class B {
public:
B(int x):x_(x) { printf("%s\n", __FUNCSIG__);foo(); }
void foo() { printf("x_=%d\n", x_); }
int x_;
};
class A {
public : int x ;
};
class D: public A, public B {
public:
D(int x): B(x) {}
};

B b = B(101);
union {
int D::*p_d ;
int v;
} u = {&B::x_};
union {
int B::*p_b ;
int v;
} u2 = {&B::x_};
D d(102);
         union { int B::* p_b; int v; } u3 = {NULL}; 
printf("B::x = %d, offset = %d\n", b.*u2.p_b, u2.v );
printf("D::B::x = %d, offset = %d\n", d.*u.p_d, u.v);
printf("NULL = %d, is null: %s \n", u3.v, (u3.p_b == NULL)?"True":"False" );

有意使用多重继承把A的子对象安排在了宿主对象D的最前面, 这样对象B中的唯一数据成员x_的偏移就是0, 而将它隐式转换为类D数据成员的指针时, 就被转换为了4, 原因是A的对象占用了最开始的4个字节. 从这个实验也看到, VC(VS2008)并没有通过加1来实现对空指针的支持. 而是把指向类成员的空指针实现为了一个特殊的值-1(0xFFFF*).

0 在编译器中, 与其它的整型常量相比有其特殊性, 常规指针和指向类成员的指针都可以与之进行 ==, != 的比较操作, 而且编译器并非把它们视之为整数进行比较, 而是执行一个与实现相关的空指针的比较, 这里就是一个最好的例子, 记得Bjarne说到在某些平台上NULL并不实现为全部为0的位模式, 当时还很想有一个这样的平台验证一下, 可惜他没提具体是哪一个平台, 其实即使提及了也很可能没机会vmware出这样一个平台, 不想在VC上通过指向类成员的指针就很容易验证这一点.

说指向类成员的指针不是常规指针, 还有其它证据:
常规指针支持:
++, --, +=, -=, 同型指针的相减. 而这些常规指针的内建操作对于类成员的指针都是非法的.
下面是代码
u3.p_b ++;
++u3.p_b ;
u3.p_b --;
--u3.p_b ;
u3.p_b += 2;
u3.p_b -= 2;

在VC(VS2008)中生成的错误信息:
error C2171: '++' : illegal on operands of type 'int B::* '
error C2171: '++' : illegal on operands of type 'int B::* '
error C2171: '--' : illegal on operands of type 'int B::* '
error C2171: '--' : illegal on operands of type 'int B::* '
error C2296: '+=' : illegal, left operand has type 'int B::* '
error C2296: '-=' : illegal, left operand has type 'int B::* '

其中出现两个++和--是因为一个是前++, 一个是后++.

关于指向成员函数的指针, 之前没想到的一点是它与virtual函数的交互, 在Item 16中, 作者提到说指向成员函数的指针往往实现为一个小的结构, 记录着该函数指针是否为virtual函数, 如果是, 还需要记录着其vtbl在对象内的偏移, 以及其相对于vtbl的偏移. virtual函数是指向成员函数的指针不能简单实现为常规函数指针的主要原因(还有其它原因?)

解读语法时的断词法, 类比于解引用常规的指针:
*p = 3;
this->p = 3;
需要
c.*p = 3;
this->*p = 3;
把c.*p 中的.看成是与*不可分隔的一部分.它们总是连续出现(但c和.*以及p中间可以有空格, 在词法上并不是一个单位)

.* 和 ->* 是独立的内建操作符, 连接左边一个类对象或对象的指针, 右边是一个该类的一个成员指针. 结果是一个该类的成员对象的左值.
阅读(1552) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~