您的位置:首页 > 编程语言 > C语言/C++

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必须在具现操作发生时候才能检查.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: