当前位置导航:炫浪网>>网络学院>>编程开发>>C++教程>>C++基础入门教程

《深度探索C++对象模型》读书笔记(7)

    ***Template的“具现”行为***

    template class中的任何member都只能通过template class的某个实体来存取或操作。
 Point<float>::Status s;  // ok
Point::Status s;  // error

    如果我们定义一个指针,指向特定的实体,像这样:
 Point<float> *ptr = 0;

    由于这是一个指向class object的指针,本身并不是一个class object,编译器不需要知道与该class有关的任何members数据。所以将“Point的一个float实体”具现也就没有必要。

    如果不是一个pointer而是reference,假设:
 Point<float> &ref = 0;

    这个定义的真正语意会被扩展为:
 // 内部扩展
Point<float> temp(float(0));
Point<float> &ref = temp;

    以上转化是因为reference并不是无物(no object)的代名词,0被视作整数,必须被转换为类型Point<float>的一个对象。

    然而,member functions只有在member functions被使用的时候,C++ Standard才要求它们被“具现”出来。这个规则的由来主要有两个原因:

    (1)空间和效率的考虑。对于未使用的函数进行“具现”将会花费大量的时间和空间;

    (2)尚未实现的功能。并不是一个template具现出来的所有类型一定能够完整支持一组member functions,因而只需具现真正需要的member functions.

    举个例子:
 Point<float> *p = new Point<float>;

    只有(a)Point template的float实例、(b)new 运算符、(c)default constructor需要被“具现”。

    ***Template的错误报告***

    所有与类型相关的检验,如果涉及到template参数,都必须延迟到真正的具现操作发生。

    对于下面的template声明:
 template <class T>
class Mumble
{
public:
Mumble(T t = 1024) : _t(t)
{
if(tt != t)
throw ex ex;
}
private:
T tt;
}

    其中像“T t = 1024”、“tt != t”这样的潜在错误在template声明时并不会报告,而会在每个具现操作发生时被检查出来并记录之,其结果将因不同的实际类型而不同。
 Mumble<int> mi;  // 上述两个潜在错误都不存在
Mumble<int*> pmi;  // 由于不能将一个非零的整数常量指定给一个指针,故“T t = 1024”错误

    ***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); }
// ...
private:
int _val;
type _member;
};

// scope of the template instantiation
extern int foo(int);

ScopeRules<int> sr0;

    在“scope of the template definition”中,只有一个foo()函数声明位于scope之内;然而在“scope of the template instantiation”中,两个foo()函数声明都位于scope之内。对于以下函数操作:
 // scope of the template instantiation
sr0.invariant();

    那么,在invariant()中调用的究竟是哪一个foo()函数实体呢?

    Template之中,对于一个nonmember name的决议结果是根据这个name的使用是否与“用以具现出该template的参数类型”有关而决定的,如果其使用互不相关,那么就以“scope of the template definition”来决定name,否则就以“scope of the template instantiation”来决定name.
 // 因为_val的类型是int,而函数的决议只和函数原型有关,与函数返回值无关
// 被用来具现这个template的真正类型对于_val的类型没有影响
_member = foo(_val);

    故此处的调用操作由“scope of the template definition”来决议。

    若是如下的函数调用:

    sr0.type_dependent();

    由于_member的类型与template参数有关,故此处由“scope of the template instantiation”来决议。

    ***Member Function的具现行为***

    以手动方式在个别的object module中完成预先具现操作,是唯一有效率的办法。

    ***执行期类型识别***

    dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的(也就是说,一个base type pointer指向一个derived class object),这个运算符会传回被适当转型过的指针;如果downcast不是安全的,这个运算符会传回0.

 typedef type *ptype;
typedef fct *pfct;

simplify_conv_op(ptype pt)
{
if(pfct pf = dynamic_cast<pfct>(pt)) {
...
}
else { ... }
}

    什么是dynamic_cast的真正成本?pfct的一个类型描述器会被编译器产生出来,由pt指向之class object类型描述器必须在执行期通过vptr取得。下面是可能的转换:
 // 取得pt的类型描述器
((type_info*)(pt->vptr[0]))->_type_description;

    其中,type_info是C++ Standard所定义的类型描述器的class名称,该class中放置着待索求的类型信息。virtual table的第一个slot内含type_info object的地址,此type_info object与pt所指之class type有关。

    dynamic_cast运算符也适用于reference身上,然而对于一个non-type-safe-cast,其结果不会与施行于指针的情况一样。一个reference不可以像指针那样“把自己设为0便代表了no object”;若将一个reference设为0,会引起一个临时性对象(拥有被参考到的类型)被产生出来,该临时对象的初值为0,这个reference然后被设定为该临时变量的一个别名。

    因而,如果reference并不真正是某一种derived class,那么可通过丢出一个bad_cast exception进行处理:
 simplify_conv_op(const type &rt)
{
try {
fct &rf = dynamic_cast<fct&>(rt);
}
catch(bad cast) {
// ...
}
}

    当然,你也可以使用typeid运算符来达到同样的目的:
 simplify_conv_op(const type &rt)
{
if(typeid(rt) == typeid(fct))
{
fct &rf = dynamic_cast<fct&>(rt);
}
else { ... }
}
相关内容
赞助商链接