考虑下面的一段程序片段
//某个foo.h头文件,从某处含入
extern float x;
//程序员的Point3D.h文件
class Point3D{
public :
Point3D(float,float,float);
//问题:被传回和被设定的x是哪一个x呢?
float X() const { return x;}
void X(float new_x) {x = new_x;}
//....
private:
float x,y,z;
.......
};
如果我问你Point3D::X()传回哪一个x?是class 内部的那个?还是外部那个?目前正确的回答是内部的,但是并不是从来是就是正确的。
在C++最早期的编译器,如果在Point3D::X()的两个函数实例中对x取用操作,该操作将会指向global x object !
因此早期C++提出了两种防御机制。
1.把所有的Data member 放在class声明起头处,以确保正确的绑定。
class Point3D{
float x,y,z;
public:
float X() const {return x;}
......
};
2.把所有的inline functions,不管大小都放在class声明之处。
class Point3D{
public:
//防御性程序设计风格2
//把所有的inline functions都移到class之外。
Point3D();
float X();
void X(float new_a);
........
};
inline float Point3D::X() const{
return x;
}
........
这种程序设计风格事实上到今天还存在,虽然他们的必要性自从C++2.0之后就消失了。
C++ standard以member scope resolution rule来提炼这个rewriting rule,其效果是,如果一个inline函数在class声明之后立刻被定义的话,那么就还是对其评估求值,也就是说当一个人写下这样的代码:
extern int x;
class Point3D{
public:
....
//对函数本体的分析将延迟,直到class声明的右大括号出现才开始
float X() const{ return x;}
......
private :
float x;
.....
};
//事实上,分析在这里进行
对member function本体的分析,会直到整个class的声明都出现了才开始,因此在一个inline member function躯体之内的一个Datamember绑定操作,会在整个class 声明完成之后才发生。
然而,这对于member function 的argument list并不为真,Argument list 中 的名称还是会在它们第一次遭遇是被适当地决议(resolved)完成,因此在extern 和nested type name 之间的非直觉绑定操作还是会发生。
typedef int length;
class Point3D{
public:
//length被决议为global
//_val被决议为Point3D::_val
void mumble(length val){ _val = val;}
length mumble() { return _val;}
private:
//length 必须在本class对它的第一个参考操作之前被看见
//这样声明将使先前操作不合法
typedef float length;
length _val;
.......
}
上述这个语言状况,任然需要某种防御性风格,请总是把nested type声明放在class的起始处。