C++对象模型——站在对象模型的尖端 (第七章)
2015-08-18 16:18
309 查看
第7章 站在对象模型的尖端 (On the Cusp of the Object Model)
这一章讨论三个著名的C++语言扩充性质,它们都会影响C++对象.它们分别是 template, exception handling(EH)和runtime type identification(RTTI).7.1 Template
C++程序设计的风格以及习惯,自从1991年的cfront 3.0引入 template 之后就改变了.原本 template 被视为是对container class 如Lists和Arrays的一项支持,但现在它已经成为通用程序设计的基础(也就是Standard Template Library,STL)的基础.它也被用于属性混合(如内存配置策略)或互斥(mutual exclusion)机制的参数化技术中,这一节的焦点放在 template 的语意上,讨论 templates 在编译系统中"何时","为什么"以及"如何"发挥其功能.下面是有关 template 的三个主要讨论方向:
1.template 的声明.基本上来说就是当声明一个 template class,template class member function等等,会发生什么事情.
2.如何"具现(instantiates)"出 class object以及 inline nonmember,以及member template functions,这些是"每一个编译单位都会拥有一份实体"的东西.
3.如何"具现(instantiates)"出nonmember以及member template functions,以及 static template members,这些都是"每一个可执行文件中只需要一份实体"的东西,这也就是一般而言 template 所带来的问题.
使用"具现(instantiation"这个字眼来表示"进程process将真正的类型和表达式绑定到template相关形式参数(formal parameters)上"的操作.例如,下面是一个 template function:
template <class Type> Type min(const Type &t1, const Type &t2) { ... }用法如下:
min(1.0, 2.0);于是进程就把Type绑定为 double 并产生min()的一个程序文本实体(并适当施以mangling手术,给它一个独一无二的名称),其中t1和t2的类型都是 double.
Template 的"具现"行为 (Template Instantiation)
考虑下面的 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); void operator delete(void *, size_t); // ... private: static Point<Type> *freeList; static int chunkSize; Type _x, _y, _z; };首先当编译器看到 template class 声明时,它会做出什么反应?在实际程序中,什么反应也没有!也就是说,上述的 static data members并不可用.nested enum 或其他 enumerators也一样.
虽然 enum Status的真正类型在所有的Point instantiations中都一样,其enumerators也是,但它们每一个都只能够通过 template Point class 的某个实体来存取或操作,因此可以这样写:
// ok Point<float>::Status s;但不能这样写:
// error Point::Status s; class a{ };同样的道理,freeList和chunkSize对程序而言也还不可用,不能够写:
// error Point::freeList;必须明确地指定类型,才能使用freeList:
// ok Point<float>::freeList;像上面这样使用 static member,会使其一份实体与Point class 的 float instantiation在程序中产生关联.如果写:
// ok Point<double>::freeList;就会出现第二个freeList实体,与Point class 的 double instantiation产生关联.
如果定义一个指针,指向特定的实体,像这样:
Point<float> *ptr = 0;这一次,程序中什么也没有发生,为什么呢?因为一个指向 class object的指针,本身并不是一个 class object,编译器不需要知道与该 class 有关的任何members的数据或object布局数据.所以将"Point的一个float实体"具现也就没有必要.
如果不是pointer而是reference,又如何?假设:
const Point<float> &ref = 0;是的,它真的会具现出一个"Point的float实体"来,这个定义的真正语意会被扩展为:
// 内部扩展 Point<float> temporary(float(0)); const Point<float> &ref = temporary;为什么呢?因为reference并不是无物(no object)的代名词.0被视为整数,必须被转换为以下类型的一个对象:
Point<float>如果没有转换的可能,这个定义就是错误的,会在编译时被挑出来.
所以,一个 class object的定义,不论是由编译器暗中地做,或是由程序员像下面这样明确地做:
const Point<float> origin;都会导致 template class 的"具现",也就是说,float instantiation的真正对象布局会被产生处理.
然而member functions不应该被"实体"化,只有在member functions被使用的时候,C++ Standard才要求它们被"具现"出来.当前的编译器并不精确遵循这项要求.之所以由使用者来主导"具现"规则,有两个主要原因:
1.空间和时间效率的考虑.如果 class 中有100个member functions,但程序只针对某个类型使用其中两个,针对另一个类型使用其中五个,那么将其他93个函数都"具现"将花费大量的时间和空间.
2.尚未实现的机能.并不是一个 template 具现出来的所有类型就一定能够完整支持一组member functions所需要的所有运算符.如果只"具现"那些真正用到的member functions,template 就能够支持那些原本可能会造成编译时期错误的类型.
例如,origin的定义需要调用Point的default constructor和destructor,那么只有这两个函数需要被"具现".类似的道理,当程序员写:
Point<float> *p = new Point<float>;时,只有(1)Point template 的 float 实例,(2)new 运算符,(3)default constructor需要被"具现"化.有趣的是,虽然 new 运算符是这个 class 的一个 implicitly static member,以至于它不能够直接处理其任何一个 nonstatic member,但它还是依赖真正的 template 参数类型,因为它的第一参数size_t代表 class 的大小.
这些函数在什么时候"具现"出来呢?当前流行两种策略:
在编译时候,那么函数将"具现"于origin和p存在的那个文件中.
在链接时候,那么编译器会被一些辅助工具重新激活.template 函数实体可能被放在这个文件中,别的文件中,或一个分离的储存位置上.
Template的错误报告 (Error Reporting within a Template)
考虑下面的 template 声明:1 template <class T> 2 class Mumble 3 { 4 public$: 5 Mumble(T t = 1024) 6 : _t(t) 7 { 8 if (tt != t) 9 throw ex ex; 10 } 11 private: 12 T tt; 13 }这个Mumble template class 的声明含有一些露骨和隐藏的错误.
L4:$字符错误,
L5:t被初始化为1024或许可以,或许不可以
L6:_t不存在,tt才存在
L8:!=运算符可能已定义好,可能没有
L9:意外的键入ex两次
L13:没有以分号作为 class 声明的结束.
在一个nontemplate class 声明中,这个6个错误会被编译器挑出来.但 template class 却不同.所有与类型有关的检验,如果涉及到 template 参数,都必须延迟到真正的具现操作发生,才能检验.即L5和L8必须在具现操作发生时候才能检查.
相关文章推荐
- 逗号表达式-----IOS开发学习记录第6天之C语言学习
- C语言预预处理命令
- C++ 子类继承父类 之 构造函数
- C++三大继承构造函数的执行顺序详解
- c++ 执行命令行获取执行结果
- C++中extern关键字使用(转)
- C++实现系统服务暂停、停止、启动
- C++stl map学习
- C++模板实现泛型队列
- IOS开发系列--C语言之基础知识
- IOS开发系列--C语言之数组和字符串
- IOS开发系列--C语言之指针
- IOS开发系列--C语言之预处理
- IOS开发系列--C语言之存储方式和作用域
- IOS开发系列--C语言之构造类型
- C++string使用详解
- C语言编译的全过程
- C++模板实现泛型链表
- 深入分析C++中char * 和char []的区别
- IOS开发学习记录第5天之C语言学习