C++类模板的对象模型

x33g5p2x  于2021-11-09 转载在 C/C++  
字(3.2k)|赞(0)|评价(0)|浏览(506)

1. Template的“实例化”行为:(Template Instantiation)

1.1 Template类模板中成员变量的实例化:

考虑下面的 template Point class:

  1. template <class Type>
  2. class Point {
  3. public:
  4. enum Status { unallocated, normalized };
  5. Point(Type x = 0.0, Type y = 0.0, Type z = 0.0);
  6. ~Point();
  7. void* operator new(size_t); //重载 new运算符
  8. void operator delete(void*, size_t); //重载 delete运算符
  9. private:
  10. static Point<Type> *free_list;
  11. static int chunkSize;
  12. Type _x, _y, _z;
  13. };

首先,当编译器看到 template class 类模板的声明时,它会做出什么反应?
在实际程序中,什么反应也没有。即使template模板类中的static静态数据成员和enum数据也都不可用,它们中的每一个必须通过template模板类的某个实体来存取或操作,例如:

  1. Point<float>::Status s; //ok
  2. Point::Status s; //error

也就是说,Point类模板中的enum和static数据会在每一个实例中都产生出来,Point 和 Point 会分别产生单独的数据。

通过对模板类型对象求sizeof大小也可以看出模板实例化的差别:

  1. template <typename _Tp>
  2. class Demo {
  3. public:
  4. _Tp a;
  5. };
  6. int main() {
  7. Demo<int> _m1;
  8. Demo<double> _m2;
  9. cout << sizeof(Demo) << endl; //编译报错
  10. cout << sizeof(_m1) << endl; //4
  11. cout << sizeof(_m2) << endl; //8
  12. return 0;
  13. }

对template类模板类型直接求sizeof将会编译报错,对实例化后的模板对象求sizeof则与其实例化的类型相关:

  1. error: use of class template 'Demo' requires template arguments
  2. cout << sizeof(Demo) << endl;

1.2 Template类模板中成员函数的实例化:

member functions 成员函数(至少对于那些未被使用过的)不应该被“初始化”。 只有在member functions被使用的时候,C++ Standard 才要求它们被“实例化”。

目前的编译器并不精确遵循这项要求。

之所以由类模板的使用者来主导“实例化”规则,有两个主要原因:

  1. 空间和时间效率的考虑:
    如果class中有100个member functions,但你的程序只针对某个类型使用其中两个,针对另一个类型使用其中五个,那么将其他193个函数都“实例化”将会花费大量的时间和空间;
  2. 尚未实现的机能:
    并不是一个template实例化的所有类型就一定能够完整支持一组member functions所需要的所有运算符。如果只“实例化”那些真正用到的member functions,template就能够支持哪些原本可能会造成编译使其错误的类型。

这些函数在什么时候“实例化”,目前流行两种策略:

  1. 在编译的时候;
  2. 在链接的时候。

对于不同类型的实例化,类模板中的成员函数也会产生多个实例。

2. Static Data Member:(类的静态数据成员)

静态数据成员独立于class之外,可以将其视为一个global全局变量。

注意:静态数据成员变量在类内只是声明,要放到类外定义和初始化,且必须要在类外初始化!

如果未对类内声明过的static成员初始化(在类外),或在类内对其初始化,则编译时将会报错。
static关键字只需要放在变量的声明处(类内),定义和初始化时不必写出,否则编译时将会报错。

实际上static静态成员变量并不存储在对象的内存空间中,所以存取static静态成员变量也不需要类对象,直接使用类名即可访问(与static静态成员函数方式相同)。

编译器会对每个类的static静态成员做“name-manling”处理,以避免不同的类中声明同名static静态成员时造成同名冲突。

举例:

  1. class A {
  2. public:
  3. static void func() { //静态函数可以通过类名直接访问,只能访问类的static静态成员,不能访问非静态普通成员
  4. cout << _a << endl;
  5. }
  6. private:
  7. static int _a; //static静态成员变量在类内只能算是声明,并未定义和初始化,此时使用_a 将会报错
  8. };
  9. ------
  10. 编译报错:
  11. /tmp/ccOZViis.o:在函数‘A::func()’中:
  12. test.cpp:(.text._ZN1A4funcEv[_ZN1A4funcEv]+0x6):对‘A::_a’未定义的引用
  13. collect2: 错误:ld 返回 1

在上面的例子中,编译是将会报错,理由是类A中的静态成员变量 static int _a 在类内只能算作声明,实际上并未定义及初始化。

正确的写法是:

  1. class A {
  2. public:
  3. static void func() {
  4. cout << _a << endl;
  5. }
  6. private:
  7. static int _a;
  8. };
  9. int A::_a = 0; //static静态成员变量必须要在类外定义,此时不用带static关键字

另外,由于static静态成员变量不属于类,所以对类求sizeof大小时,也不会包含static静态成员变量。
例如:

  1. class A {
  2. static int _a; //类中将不会包含 _a 的大小
  3. int _b;
  4. };
  5. int main() {
  6. cout << sizeof(int) << '\n';
  7. cout << sizeof(A) << '\n';
  8. }
  9. ------
  10. 4
  11. 4

3. NonStatic & Static Member Function:(类的非静态&静态成员函数)

类的静态成员函数与非静态成员函数都是存储在代码段中,
C++的设计准则之一是 非静态成员函数 至少必须与一般的非成员函数具有相同的效率。
所以成员函数虽然是写在类的内部,而实际上在编译阶段,编译器会将其转换为非成员函数,二者在底层上并无什么区别。

将一个非静态成员函数改写为一般的非成员函数,大致经过三个步骤:

  1. 改写成员函数的原型,增加一个形参,接收一个类类型的this指针;
  2. 改写成员函数的内部对类的非静态成员变量的操作方式,改为经由this指针操作;
  3. 改写成员函数为一个外部函数,将函数名经“name-manling”处理。
  1. class Point3d {
  2. float magnitude3d() {}
  3. };
  4. ---> 1. 改写成员函数增加*this形参:
  5. float magnitude3d(Point3d const *this) {} //this指针是指针常量,指向固定
  6. ---> 2. 改写成员函数内部访问成员变量的方式为通过this指针操作:
  7. float magnitude3d(Point3d const *this) { return this->x; }
  8. ---> 3. 改写成员函数名为“name-manling”格式:
  9. float Point3d_magnitude3d(Point3d const *this) { return this->x; }

类的静态成员函数的特点是:

  1. 没有this指针,因此不能操作类的非静态成员变量;
  2. 但可以不必非要经过类对象调用,可由类名直接调用。

相关文章