1,Template(模板):
template被用于属性混合(如内存配置策略)或互斥机制(使用于线程同步化控制)的参数化技术之中。它甚至被使用于template metaprograms(模板元编程)技术:class expression templates(类表达式模板)将在编译时期而非执行期被评估,因而带来重大的效率提升。
2,template的三个主要的讨论方向:
(1),template的声明,也就是说当你声明一个template class、template class member function等时,会发生什么事情。
(2),如何”实例化“class object、inline nonmember以及member template functions。这些是”每一个编译单位都会拥有一份实例“的东西。
(3),如何”实例化“nonmember、member template functions以及static template class members。这些都是每一个可执行文件中只需要一份实例的东西。
PS:其中实例化表示进程将真正的类型和表达式绑定到template相关形式参数上。
3,有如下template 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);
void operator delete(void*,size_t);
private:
static Point<Type> *freeList;
static int chunkSize;
Type _x,_y,_z;
};
当编译器看到template class的时候不会有任何反应,也就是说static data member并不可用,嵌套enum也一样。
其中,enum Status的真正类型在所有的Point 实例中都一样。但我们依旧只能通过template Point class的某个实例来存取和操作。即我们可以这样写:
Point<float>::Status s;//正确
但不能这样写:
Point::Status s;//错误
同样freeLsit和chunkSize对程序而言也不可用。
Point::freeList;//错误
Point::chunkSize;//错误
Point<float>::freeList;//正确
Point<float>::chunkSize;//正确
有如下语句:
const Point<float> &ref = 0;
此时,编译器会实例化一个Point的float实例。
它会被扩展为一下形式:
//内部扩展
Point<float> temp(float(0));
const Point<float> &ref = temp;
因为引用并不是无物的代名词,0被视为整数,必须被转化为一下类型的一个对象:
Point<float>
一个class object的定义,不论是由编译器暗中的做(像temp那样),或是由程序员显式的做:
const Point<float> origin;
都会导致template class 的实例化。
然而,member function(成员函数)(至少对那些未被使用过的)不应该被实例化,只有在被使用的时候,C++标准才要求它们被实例化。
由使用者来主导”实例化“规则主要有两个原因:
(1),空间和时间效率的考虑。因为一个类可能会有很多member functions,但一个class实例并不一定会用到所有的member function。
(2),尚未实现的机能。
例如:origin的定义需要调用Point的默认构造函数和析构函数,因此只有这两个函数需要被实例化。
4,什么时候实例化这些函数:
目前有两种策略:
(1),在编译的时候;(2),在链接的时候。
5,template的错误报告
在template class中,所有与类型有关的检验,如果牵扯到template参数,都必须延迟到真正的实例化操作发生才进行。
6,Template中的名称决议法
scope of the template definition(定义出template的域)
scope of the template instantiation(实例化template的域)
//scope of the template definition
extern double foo( double );
template<class type>
class ScopeRules
{
public:
void invariant()
{
_member = foo(_val);
}
type type_dependent()
{
return foo(_member);//书中此处我觉得有问题,因为type的具体值是什么还不知道
//那么如果类型不匹配就无法成功调用foo()函数。还请网友指点一下
}
private:
int _val;
type _member;
};
//scope of template instantiation
extern int foo( int );
ScopeRules<int> sr0;
此时如果有以下调用:
sr0.invariant();
那么在invariant()中调用的究竟是哪个foo()函数实例呢?
答案:调用的是
extern double foo( double );
因为:
在Template中,对于一个nonmember name(非成员名称)的决议结果,是根据这个name的使用是否 与”用以实例化该template的参数类型“有关而决定的。
如果不相关,就使用scope of the template definition来决定name;
如果相关,就使用scope of template instantiation来决定name;
如果此时有如下调用:
sr0.type_dependent();
那么此时会调用scope of template instantiation中声明的foo()函数,而在该例子中,共有两个foo()函 数,且此例的_member类型为int,所以调用
extern int foo( int );
如果是
ScopeRules< double > sr0;
那么就会调用
extern double foo( double );
不管如何演变,都是由“scope of template instantiation”来决定。
总结如下:
scope of template definition//用以专注于一般的template class
scope of template instantiation//用以专注于特定的实例
7,Member function的实例化行为
template functions的实例化:
目前有两个策略,一个是编译时期策略,另一个是链接时期策略。
但这两个策略都有一个共同的缺点:当template实例被产生出来时,有时候会大量增加编译时间。
8,异常处理
当一个异常发生时,编译系统必须完成以下事情:
(1),检验发生throw操作的函数
(2),觉得throw操作是否发生在try区段中。
(3),若是,编译系统必须把异常类型拿来和每一个catch子句进行比较。
(4),如果比较吻合,流程控制应该交到catch子句手中。
(5),如果throw的发生并不在try区段中,或没有一个catch子句吻合,那么系统必须(a)摧毁所有已构造的局部对象,(b)从堆栈中将木目前的函数“unwind”(解除)掉。(c)进行到程序堆栈的下一个函数中去,然后重复上述步骤2~5。
----当一个异常被抛出时,异常对象会被产生出来并通常放置在相同形式的异常数据堆栈中。从throw端传给catch子句的,是异常对象的地址、类型描述器(或是一个函数指针,该函数会传回与该异常类型有关的类型描述器对象)以及可能会有的异常对象描述器
9,(Type-safe Downcast)保证安全的向下转换操作:
一个保证安全的向下转换操作必须在执行期对指针有所查询,看看它是否指向它所展现的对象的真正类型。因此,要想支持type-safe downcast,在对象空间和执行时间上都需要有一些额外负担:
(1)需要额外的空间以存储类型信息,通常是一个指针,指向某个类型信息节点。
(2)需要额外的时间以决定执行期的类型(run type),因为,这需要在执行期才能决定。
C++的RTTI(运行时类型识别)机制提供了一个安全的downcast设备,但只对那些展现多态(也就是使用继承和动态绑定)的类型有效。
而如何分辨出这些类型呢?
答:通过声明一个活多个虚拟函数来区别class声明。
在C++中,一个具备多态性质的class,正是内含继承而来(或直接声明)的虚拟函数。
10,引用不是指针:
程序执行中对一个class指针类型施以dynamic_cast运算符,会获得true或false:
(1),如果传回真正的地址,则表示这一对象的动态类型被确认了,一些与类型有关的操作现在可以施行于其上。
(2),如果传回0,则表示没有指向任何对象,意味着应该以另一种逻辑施行于这个动态类型未确定的对象身上。
dynamic_cast运算符也适用于引用上,然而对于一个non-type-safe cast,其结果不会与施行于指针的情况 相同。因为引用不可以像指针那样“把自己设为0来表示”no object“”。
Waring:若将一个引用设为0,会引起一个临时对象被产生出来,且该对象的初值为0。
因此,当dynamic_cast施行于一个引用时,会发生下列事情:
(1),如果引用真正参考到适当的继承类,downcast会被执行而程序可以继续进行。
(2),如果引用并不真正是某一种继承类时,那么,由于不能传回0,因此抛出一个bad_cast 异常。