非原创,在学习
7 站在对象模型的尖端 On the Cusp of the Object Model
这一章讨论三个著名的C++语言扩充性质,它们都会影响CH+对象。它们分别是 template、exception handling (EH)和runtime type identification (RTTI)
模版、异常、通过运行时类型识别
7.1 template
下面是有关template的三个主要讨论方向:
1. templatc的声明。基本上来说就是当你声明一个template class、templateclass member function等等时,会发生什么事情。
2.如何“具现( instantiates)”出class object以及inline nonmember,以及member template functions,这些是“每一个编译单位都会拥有一份实体”的东西。
3.如何“具现( instantiates)”出nonmember以及member template functions以及static template class members,这些都是“每一个可执行文件中只需要一份实体”的东西。这也就是一般而言template所带来的问题。
下面是一个template function;:
namespace Test24 {
template<class Type>
Type min(const Type& t1, const Type& t2) {
// ...
}
void test()
{
min(1.0, 2.0);
}
}
于是Type 被绑定为double 并产生min()的一个程序文字实体(并适当施以“mangling”手术,给它一个独一无二的名称),其中tl 和 t2的类型都是double。
Template的“具现”行为( Template Instantiation )
考虑下面的template Point class:
namespace Test25
{
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;
};
}
枚举和类型没有关系,但是不能这样写:
Point::Status s; // error
必须这样写:
Point<float>::Status s;
同样的道理,freeList和chunksize也得这样写。
如果定义一个指针,指向特定的实体,像这样:
Point<float>* ptr = 0;
这一次,程序中什么也没有发生。为什么呢?因为一个指向class object的指针,本身并不是一个 class object,编译器不需要知道与该class有关的任何members的数据或object布局数据.所以将“Point的一个float实体”具现也就没有必要。
如果不是指针而是reference呢
const Point<float> &ref = 0;
是的,它真的会具现出一个“Point的 float实体”来.这个定义的真正语意会被扩展为:
// 内部扩展
Point<float> temporary(float(0));
const Point<float>& ref = temporary;
为什么呢?因为reference 并不是无物( no object)的代名词.0被视为整数,必须被转换为以下类型的一个对象。
然而,member functions(至少对于那些未被使用过的)不应该被“实体”化,只有在 member functions被使用的时候,C++ Standard才要求它们被“具现”出来。当前的编译器并不精确遵循这项要求。之所以由使用者来主导“具现"( instantiantion)规则,有两个主要原因:
1.空间和时间效率的考虑。如果class中有100个member functions,但你的程序只针对某个类型使用其中两个,针对另一个类型使用其中五个,那么将其它193个函数都“具现”将会花费大量的时间和空间。
2.尚未实现的机能。并不是一个template具现出来的所有类型就一定能够完整支持一组member functions所需要的所有运算符。如果只“具现’那些真正用到的member functions,template就能够支持那些原本可能会造成编译时期错误的类型( types) .
Template 的错误报告(Error Reporting within a Template)
明显的错误:
namespace Test26
{
template <class T>
class Mumble
{
// $、t被初始化为1024,不确定对不对、tt是成员变量,_t不是
public$ : Mumble(T t = 1024) : _t(t)
{
// != 需要重载
if (tt != t)
{
// ex
throw ex ex;
}
}
private:
T tt;
} // 没有;结尾
}
运行
按报错修改:
这句是正确的???
if (tt != t)
目前的编译器,面对一个template声明,在它被一组实际参数具现之前,只能施行以有限的错误检查。template中那些与语法无关的错误,程序员可能认为十分明显,编译器却让它通过了,只有在特定实体被定义之后,才会发出抱怨这是目前实现技术上的一个大问题。
写模版的时候,一定一定要特别注意。
Template 中的名称决议方式(Name Resolution within a Template)
必须能够区分以下调种意义。一种是C+t Standard所谓的“scope of thetemplate definition”,也就是“定义出template”的程序。另一种是C++ Standard所谓的“scope of the iemplase instantiation”,也就是“具现出template”的程序。
第一个是模版程序
namespace Test27
{
// 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);
}
// ...
private:
int _val;
type _member;
};
}
第二个是模版实例化程序
extern int foo(int);
// ...
ScopeRules<int> sr0;
在ScopeRules template中有两个foo()调用操作。在“scope of template definition”中,只有一个foo()函数声明位于scope 之内。然而在“scope of template instantiation”中,两个foo()函数声明都位于scope 之内。如果我们有一个函数调用操作:
sr0.invariant();
那么,在 invariant(中调用的究竟是哪一个foo()函数实体呢?
_mamber = foo(_val);
在调用操作的那一点上,程序中的两个函数实体是:
// scope of the template declaration
extern double foo (double);
// scope of the template instantiation
extern int foo (int);
测试一下
namespace Test27
{
// scope of the template definition
extern double foo(double) {
std::cout << "extern double foo(double)" << std::endl;
}
template <class type>
class ScopeRules
{
public:
void invariant()
{
_member = foo(_val);
}
type type_dependent()
{
return foo(_member);
}
// ...
private:
int _val;
type _member;
};
extern int foo(int) {
std::cout << "extern int foo(int)" << std::endl;
}
// ...
ScopeRules<int> sr0;
void test() {
ScopeRules<int> sr0;
sr0.invariant();
}
}
int main(int argc, char **argv)
{
Test27::test();
system("pause");
return 0;
}
竟然调用的是double这个(似乎和函数出现的顺序有关)
Template 之中,对于一个nonmember name 的决议结果是根据这个name 的使用是否与“用以具现出该template的参数类型”有关而决定的.如果其使用互不相关,那么就以“scope of the template declaration”来决定name。如果其使用互有关联,那么就以“scope of the template instantiation”来决定name。
另外看看“与类型相关”( type-dependent)的用法:
sr0.type_dependent();
结果
这意味着一个编译器必须保持两个scope contexts:
1. “scope of the template declaration”,用以专注于一般的 template class
2. “scope of the template instantiation”,用以专注于特定的实体。编译器的决议( resolution)算法必须决定哪一个才是适当的 scope,然后在其中搜寻适当的name.