1. Template的“实例化”行为:(Template Instantiation)
1.1 Template类模板中成员变量的实例化:
考虑下面的 template Point class:
template <class Type>
class Point {
public:
enum Status { unallocated, normalized };
Point(Type x = 0.0, Type y = 0.0, Type z = 0.0);
~Point();
void* operator new(size_t); //重载 new运算符
void operator delete(void*, size_t); //重载 delete运算符
private:
static Point<Type> *free_list;
static int chunkSize;
Type _x, _y, _z;
};
首先,当编译器看到 template class 类模板的声明时,它会做出什么反应?
在实际程序中,什么反应也没有。即使template模板类中的static静态数据成员和enum数据也都不可用,它们中的每一个必须通过template模板类的某个实体来存取或操作,例如:
Point<float>::Status s; //ok
Point::Status s; //error
也就是说,Point类模板中的enum和static数据会在每一个实例中都产生出来,Point 和 Point 会分别产生单独的数据。
通过对模板类型对象求sizeof大小也可以看出模板实例化的差别:
template <typename _Tp>
class Demo {
public:
_Tp a;
};
int main() {
Demo<int> _m1;
Demo<double> _m2;
cout << sizeof(Demo) << endl; //编译报错
cout << sizeof(_m1) << endl; //4
cout << sizeof(_m2) << endl; //8
return 0;
}
对template类模板类型直接求sizeof将会编译报错,对实例化后的模板对象求sizeof则与其实例化的类型相关:
error: use of class template 'Demo' requires template arguments
cout << sizeof(Demo) << endl;
1.2 Template类模板中成员函数的实例化:
member functions 成员函数(至少对于那些未被使用过的)不应该被“初始化”。 只有在member functions被使用的时候,C++ Standard 才要求它们被“实例化”。
目前的编译器并不精确遵循这项要求。
之所以由类模板的使用者来主导“实例化”规则,有两个主要原因:
- 空间和时间效率的考虑:
如果class中有100个member functions,但你的程序只针对某个类型使用其中两个,针对另一个类型使用其中五个,那么将其他193个函数都“实例化”将会花费大量的时间和空间; - 尚未实现的机能:
并不是一个template实例化的所有类型就一定能够完整支持一组member functions所需要的所有运算符。如果只“实例化”那些真正用到的member functions,template就能够支持哪些原本可能会造成编译使其错误的类型。
这些函数在什么时候“实例化”,目前流行两种策略:
- 在编译的时候;
- 在链接的时候。
对于不同类型的实例化,类模板中的成员函数也会产生多个实例。
2. Static Data Member:(类的静态数据成员)
静态数据成员独立于class之外,可以将其视为一个global全局变量。
注意:静态数据成员变量在类内只是声明,要放到类外定义和初始化,且必须要在类外初始化!
如果未对类内声明过的static成员初始化(在类外),或在类内对其初始化,则编译时将会报错。
static关键字只需要放在变量的声明处(类内),定义和初始化时不必写出,否则编译时将会报错。
实际上static静态成员变量并不存储在对象的内存空间中,所以存取static静态成员变量也不需要类对象,直接使用类名即可访问(与static静态成员函数方式相同)。
编译器会对每个类的static静态成员做“name-manling”处理,以避免不同的类中声明同名static静态成员时造成同名冲突。
举例:
class A {
public:
static void func() { //静态函数可以通过类名直接访问,只能访问类的static静态成员,不能访问非静态普通成员
cout << _a << endl;
}
private:
static int _a; //static静态成员变量在类内只能算是声明,并未定义和初始化,此时使用_a 将会报错
};
------
编译报错:
/tmp/ccOZViis.o:在函数‘A::func()’中:
test.cpp:(.text._ZN1A4funcEv[_ZN1A4funcEv]+0x6):对‘A::_a’未定义的引用
collect2: 错误:ld 返回 1
在上面的例子中,编译是将会报错,理由是类A中的静态成员变量 static int _a
在类内只能算作声明,实际上并未定义及初始化。
正确的写法是:
class A {
public:
static void func() {
cout << _a << endl;
}
private:
static int _a;
};
int A::_a = 0; //static静态成员变量必须要在类外定义,此时不用带static关键字
另外,由于static静态成员变量不属于类,所以对类求sizeof大小时,也不会包含static静态成员变量。
例如:
class A {
static int _a; //类中将不会包含 _a 的大小
int _b;
};
int main() {
cout << sizeof(int) << '\n';
cout << sizeof(A) << '\n';
}
------
4
4
3. NonStatic & Static Member Function:(类的非静态&静态成员函数)
类的静态成员函数与非静态成员函数都是存储在代码段中,
C++的设计准则之一是 非静态成员函数 至少必须与一般的非成员函数具有相同的效率。
所以成员函数虽然是写在类的内部,而实际上在编译阶段,编译器会将其转换为非成员函数,二者在底层上并无什么区别。
将一个非静态成员函数改写为一般的非成员函数,大致经过三个步骤:
- 改写成员函数的原型,增加一个形参,接收一个类类型的this指针;
- 改写成员函数的内部对类的非静态成员变量的操作方式,改为经由this指针操作;
- 改写成员函数为一个外部函数,将函数名经“name-manling”处理。
class Point3d {
float magnitude3d() {}
};
---> 1. 改写成员函数增加*this形参:
float magnitude3d(Point3d const *this) {} //this指针是指针常量,指向固定
---> 2. 改写成员函数内部访问成员变量的方式为通过this指针操作:
float magnitude3d(Point3d const *this) { return this->x; }
---> 3. 改写成员函数名为“name-manling”格式:
float Point3d_magnitude3d(Point3d const *this) { return this->x; }
类的静态成员函数的特点是:
- 没有this指针,因此不能操作类的非静态成员变量;
- 但可以不必非要经过类对象调用,可由类名直接调用。