C++对象模型那点事儿(成员篇)(二)

2014-11-24 12:59:00 · 作者: · 浏览: 6
s 那么它是怎么做到的呢? 道理很简单,编译器暗地里已经向member函数实例转换为对等的nonmember 函数实例。 举个例子:
//假设Point3d中有如下一个成员函数
Point3d Point3d::magnitude(){
	//具体实现不是我们所关心的
}
//被编译器转化为(此处先不涉及name-mangling)===>>
Point3d Point3d::magnitude(Point3d *const this){
	//具体实现不是我们所关心的
}
//如果member function为const,则被转化为(此处先不涉及name-mangling)==>>
Point3d Point3d::magnitude(const Point3d *const this){
	//具体实现不是我们所关心的
}

是的,你没有看错,编译器会在member function的参数列表中加入一个指向该对象本身的this指针。至于在参数列表的头部还是尾部加入则可不比深究。所以,外界无法访问到member functions,因为参数列表不匹配。 然后再有mangling生成一个新的函数名,成为一个独一无二的外部函数。所以即使参数列表匹配也无法进行访问,因为函数名字也改变了。 老问题:
Point3d obj,*pt;
pt = &obj;
obj.magnitude();
pt->magnitude();

大家觉得上述两种函数的调用有无重大差异? 下面,我们来看看经过编译器的mangling算法转化后的样子。
obj.magnitude();
//==>>
magnitude_7Point3dFv(&obj);

pt->magnitude();
//==>>
magnitude_7Point3dFv(pt);

显然,几乎没有什么区别。 大家现在是不是对nonstatic member function有一定的了解了呢?那么,我们接着看static member functions吧。

3.2 静态成员函数


static member functions与nonstatic member functions的重大差异在于static member functions没有this指针。那么,必然导致以下结果: 1 它不能直接存取class中的nonstatic data members; 2 其不能被声明为const,volatile或virtual。 3 其不需要经由对象来调用,虽然我们一般都是用对象在调用之。 一个static member function 几乎就是经过mangling的nonstatic member function。 我们来看看mangling对static member function的转化:
//假设count()为Point3d中的一个static member function
unsigned int Point3d::count(){
	//.....
}
//===>>
unsigned int count_5Point3dSFV(){
}

函数名中的大写字母S就代表着static。 我们还有一个证据,看下面的例子:
&point3d::count()

大家猜猜得到的值得类型是什么样子的?unsigned int (Point3d::*)()还是unsigned int (*)() ? 答案显然是后者,static member function俨然已是半个nonmember function了。 那么我们再来看看
obj.count();
pt->count();

两者有无重大差异? 显然没有了this指针以后,count()会被转化为一般的nonmember 函数:
count_7Point3dSFV();

两者的调用几乎一样。

3.3 虚成员函数


我们大家都知道的是对象中会有一个虚表指针,对应的虚表中有各个虚函数的slot。 这个地方水有点深,我不想讨论那么深,原因有二: 1 自己没把握把这个地方说透。 2 并不是所有人都对那么深的东西感兴趣。 感兴趣的朋友可以查阅相关资料。 虚成员函数与nonstatic 成员函数的区别在于其存在于虚表中。 我们直接看下面的例子:
//假设 Point3d 中的第一个虚函数为normalize(),那么
Point3d obj,*pt;
pt = &obj;
pt->normalize();
obj.normalize();

pt->normalize();要想知道具体函数调用normalize()是哪个,就必须得知道pt所指对象的类型。在这个过程中我们需要知道两个信息: 1 pt所指对象的类型信息。 2 virtual function的偏移量。 一般做法是将这两样信息加入虚表中,即可在编译期间获知其具体调用。然而,visual studio 2010似乎不是这样做的。其具体做法还有待考究。 上述说的是单一继承,多重继承的时候会麻烦一些。 在vs2010下面,一个derived class内含n-1个额外的virtual table ,n表示其上一层base class的个数(单一继承不会有额外的virtual table)。 我们来看一个例子:
class Base1{
public:
	Base1();
	virtual ~base1();
	virtual Base1 *clone()const;
protected:
	float data_Base1;
};
class Base2{
public:
	Bsae2();
	virtual ~Base2();
	virtual Base2 *clone()const;
protected:
	float data_Base2;
};
class Derived:public Base1,public Base2{
public:
	Derived();
	virtual ~Derived();
	virtual Derived *clone()const;
protected:
	float data_Derived;
};

内存布局图如下所示: \
我们来看下面一组操作:
Base2 *phase2 = new Derived;

编译器会将上述代码翻译如下:
Derived *tmp = new Derived;
Base2 *phase2 = tmp  tmp+sizeof(Base1):0;

新的Derived对象的地址必须调整以指向其Base2子对象。大家现在是否明白了基类指针释放子类对象的时候如果不将析构函数声明为虚函数就不能释放完全的原因了吧! 然而,对于sun编译器来说,上述形式并不适用,其为了调节执行期间连接器的效率,将多个virtual table连锁为一个。感兴趣的朋友自行查阅相关资料。 我们这里没有讨论虚拟继承下的virtual function。 接着上面的话题:
pt->normalize();
obj.normalize();

两者区别在哪? 首先,pt->normalize();被内部转化为:(*pt->vptr[0])(pt);这点毋庸置疑。 vptr为指向虚表的指针,0为内部偏移量,pt为zhis指针。 obj.normalize();被内部转化为:(*obj.vptr[0])(&o