考虑以下程序:1
2
3
4
5
6
7
8
9
10extern float x;//外部的x变量
class Point3d{
public:
Point3d(float,float,float);
float X() const {return x;}
void X(float new_x) const {x=new_x;}
...
private:
float x,y,z;
};
如果我们调用Point3d::X()
,那么它返回的是Point3d::x
还是extern x
?熟悉C++的人都明白是前者,但并非一直以来都是前者。
在C++最早的编译器上,如果在Point3d::X()的两个函数实例中对x进行参阅(取用)操作,这操作将会指向global x object。这种结果基本上不在开发者的预期之内,因此直接导致了C++早期的两种防御性程序设计风格:
- 将所有的data members放在class声明起头处,以确保正确的绑定:
1
2
3
4
5class Point3d{
float x,y,z;
public:
...
} - 将所有的inline functions无论大小都置于声明之外
1
2
3
4
5
6
7
8
9class Point3d{
public:
Point3d(float,float,float);
float X() const;
void X(float) const;
};
inline float Point3d::X() const {
return x;
}
这些程序设计风格直至今日仍然存在,虽然已经不再具备必要性。这个古老的语言规则被称为“member scope resolution rules”,大意是“一个inline函数实体,在整个class声明未被完全看见之前,是不会被evaluated的”。C++ Standard以“member scope resolution rules”来精炼这个“rewriting rule”,其效果是,如果一个inline函数在class声明后立刻被定义的话,那么还是对其evaluate,也就是说,1
2
3
4
5
6
7
8
9
10extern float x;
class Point3d{
public:
//对函数的分析将延迟至class声明的右大括号出现才开始
float X() const {return x;}
...
private:
float x,y,z;
};
//分析在这里进行
因此,在一个inline member function躯体内的一个data member绑定操作,会在整个class声明完成后才完成。
然而,这对于member function的argument list并不为真。Argument list中的名称还是会在它们第一次遭遇时被适当地resloved。因此在extern与nested type names之间的非直觉操作绑定操作还是会发生。例如,在下面的程序段中,length的类型在两个member function signatures中都被reslove为global typedef,也就是int。当后续再有length的nested typedef声明出现时,C++
Standard就把之前的绑定标示为非法:1
2
3
4
5
6
7
8
9
10
11
12
13typedef int length;
class Point3d{
public:
//length被resloved为global
//_val被resloved为Point3d::_val
void mumble(length val) {_val = val;}
length mumble() {return _val;}
private:
//length必须在“本class对它的第一个参考操作”之前被看见
//该声明将使之前的参考操作不合法
typedef float length;
length _val;
};
上述这种语言状况,仍然需要某种防御性编程风格:请始终将nested type声明放在class的起始处。在上述例子中,如果把length的nested type定义于“在class中被参考”之前,就可以确保非直觉绑定的正确性。