介绍 在本系列的教程中,我要讨论一些ATL的内部工作方式以及它所使用的技术。
在讨论的开始,让我们先看看一个程序的内存分布。首先,编写一个简单的程序,它没有任何的数据成员,你可以看看它的内存结构。
程序1. #include <iostream>
using namespace std;
class Class {
};
int main() {
Class objClass;
cout << "Size of object is = " << sizeof(objClass) << endl;
cout << "Address of object is = " << &objClass << endl;
return 0;
}
这个程序的输出为:
Size of object is = 1
Address of object is = 0012FF7C
现在,如果我们向类中添加一些数据成员,那么这个类的大小就会是各个成员的大小之和。对于模板,也依然是这样:
程序2. #include <iostream>
using namespace std;
template <typename T>
class CPoint {
public:
T m_x;
T m_y;
};
int main() {
CPoint<int> objPoint;
cout << "Size of object is = " << sizeof(objPoint) << endl;
cout << "Address of object is = " << &objPoint << endl;
return 0;
}
现在程序的输出为:
Size of object is = 8
Address of object is = 0012FF78
那么,再向程序中添加继承。现在我们使Point3D类继承自Point类,然后来看看程序的内存结构:
程序3. #include <iostream>
using namespace std;
template <typename T>
class CPoint {
public:
T m_x;
T m_y;
};
template <typename T>
class CPoint3D : public CPoint<T> {
public:
T m_z;
};
int main() {
CPoint<int> objPoint;
cout << "Size of object Point is = " << sizeof(objPoint) << endl;
cout << "Address of object Point is = " << &objPoint << endl;
CPoint3D<int> objPoint3D;
cout << "Size of object Point3D is = " << sizeof(objPoint3D) << endl;
cout << "Address of object Point3D is = " << &objPoint3D << endl;
return 0;
}
程序的输出为:
Size of object Point is = 8
Address of object Point is = 0012FF78
Size of object Point3D is = 12
Address of object Point3D is = 0012FF6C
这一程序演示了派生类的内存结构,它表明派生类的对象所占据的内存为它本身的数据成员和它基类的成员之和。
当虚函数加入到这个派对中的时候,一切就变得都有意思了。请看下面的程序:
程序4. #include <iostream>
using namespace std;
class Class {
public:
virtual void fun() { cout << "Class::fun" << endl; }
};
int main() {
Class objClass;
cout << "Size of Class = " << sizeof(objClass) << endl;
cout << "Address of Class = " << &objClass << endl;
return 0;
}
程序的输出为:
Size of Class = 4
Address of Class = 0012FF7C
并且,在我们添加了多于一个的虚函数之后,会变得更加有趣:
程序5. #include <iostream>
using namespace std;
class Class {
public:
virtual void fun1() { cout << "Class::fun1" << endl; }
virtual void fun2() { cout << "Class::fun2" << endl; }
virtual void fun3() { cout << "Class::fun3" << endl; }
};
int main() {
Class objClass;
cout << "Size of Class = " << sizeof(objClass) << endl;
cout << "Address of Class = " << &objClass << endl;
return 0;
}
这个程序的输出和前一个程序一模一样,让我们再做一个实验来更好地弄懂这件事吧。
程序6. #include <iostream>
using namespace std;
class CPoint {
public:
int m_ix;
int m_iy;
virtual ~CPoint() { } // 译注:原文此处有分号,我将其去掉,下皆同
};
int main() {
CPoint objPoint;
cout << "Size of Class = " << sizeof(objPoint) << endl;
cout << "Address of Class = " << &objPoint << endl;
return 0;
}
程序的输出为:
Size of Class = 12
Address of Class = 0012FF68
这些程序的输出表明,当你向类中添加了虚函数之后,那么它的尺寸就会增加一个int的大小。在Visual
C++中也就是增加4个字节。这就意味着这个类中有三个整数大小的位置,一个是x,一个是y,另一个是处理虚函数之用的虚函数表指针。首先,让我们来看看这个新的位置,也就是这个位于对象首部(或末尾)的虚函数表指针。要这么做的话,我们需要直接存取对象所占用的内存。我们可以使用神奇的指针技术,即用一个指向int的指针来
存储一个对象的地址。
程序7. #include <iostream>
using namespace std;
class CPoint {
public:
int m_ix;
int m_iy;
CPoint(const int p_ix = 0, const int p_iy = 0) :
m_ix(p_ix), m_iy(p_iy) {
}
int getX() const {
return m_ix;
}
int getY() const {
return m_iy;
}
virtual ~CPoint() { }
};
int main() {
CPoint objPoint(5, 10);
int* pInt = (int*)&objPoint;
*(pInt+0) = 100; // 企图改变x的值
*(pInt+1) = 200; // 企图改变y的值
cout << "X = " << objPoint.getX() << endl;
cout << "Y = " << objPoint.getY() << endl;
return 0;
}
这个程序中最重要的东西是:
int* pInt = (int*)&objPoint;
*(pInt+0) = 100; // 企图改变x的值
*(pInt+1) = 200; // 企图改变y的值
其中,我们在把对象的地址存入一个整型指针之后,就可以把这个对象看作一个整型的指针了。程序的输出为:
X = 200
Y = 10
当然,这并不是我们想要的结果,它表明200
存储在了m_ix数据成员的位置。这就意味着m_ix,也就是第一个成员变量,是起始于内存中第二个位置的,而不是第一个。换句话说,第一个成员是虚函数表指针,其它的才是对象的数据成员。那么,只需要修改以下的两行:
int* pInt = (int*)&objPoint;
*(pInt+1) = 100; // 企图改变x的值
*(pInt+2) = 200; // 企图改变y的值
这样我们就会获得想要的结果了,以下为完整程序:
程序8. #include <iostream>
using namespace std;
class CPoint {
public:
int m_ix;
int m_iy;
CPoint(const int p_ix = 0, const int p_iy = 0) :
m_ix(p_ix), m_iy(p_iy) {
}
int getX() const {
return m_ix;
}
int getY() const {
return m_iy;
}
virtual ~CPoint() { }
};
int main() {
CPoint objPoint(5, 10);
int* pInt = (int*)&objPoint;
*(pInt+1) = 100; // 企图改变x的值
*(pInt+2) = 200; // 企图改变y的值
cout << "X = " << objPoint.getX() << endl;
cout << "Y = " << objPoint.getY() << endl;
return 0;
}
并且,程序的输出为:
X = 100
Y = 200
下图清楚地示范了当我们向类中添加了虚函数之后,虚函数表指针就会被添加在内存结构中的第一个位置。

现在问题出现了:虚函数表指针中到底存储了什么?那么看看下面的程序:
程序9. #include <iostream>
using namespace std;
class Class {
virtual void fun() { cout << "Class::fun" << endl; }
};
int main() {
Class objClass;
cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
cout << "Value at virtual pointer " << (int*)*(int*)(&objClass+0) << endl;
return 0;
}
程序的输出为:
Address of virtual pointer 0012FF7C
Value at virtual pointer 0046C060
虚函表数