深入探索C++对象模型之站在对象模型的顶端

1 模板

具象化:这里的具象化是指编译器需要针对指定的类型参数实例化一个版本的类对象或者函数版本。

1.1 模板具象化时机

  对于一个模板类的指针或者引用,何时实例化一个具象化版本的模板类,根本依据是当前声明的对象是否需要一个类的完整信息,如果不需要则不进行实例化,如果需要则进行。
  模板类的成员函数应不应该被具象化,也就是说当需要具象化一个版本的类时,需不需要把其对应的具象化版本的成员函数也具象化出来。C++标准要求只有当成员函数被使用时才进行相关的具象化。但是并不是所有的编译器都会遵从这个要求,主要因为:

  1. 如果大量的具象化成员函数仅仅调用了少数,则空间和时间效率上得不偿失;
  2. 如果仅仅具象化那些使用的成员函数,模板可能支持一些错误的类型。
      一般的策略有两种方式:
  3. 在编译阶段,函数具象化于具体的对象的文件中;
  4. 在连接时,编译器会被一些辅助工具重新激活,模板函数实体可能被放在当前文件、其他文件或者一个分离的存储上。

1.2 模板错误报告

  如果模板中包含一些常见的语法错误和一些和模板需要的类型信息相关的错误,那么何时进行错误检查,毕竟编译器不可能遇见所有的情况。一般的情况是编译器针对不同类型的错误进行不同的错误判断:

  1. 如果错误类型和模板类依赖的类型无关,比如多个分号之类,则可直接报错;
  2. 如果错误类型和模板类依赖的类型相关,则需要编译器在模板类被具象化之时才能进行具体的语法检查。

1.3 模板名字决议

double foo(double val)
{
	cout<<"extern double foo"<<endl;
	return 0.0;
}

template <class type>
class ScopeRules {
public:
    void invariant() {
        _member = foo(_val);
    }
    type type_dependent() {
        return foo(_member);
    }
    // ...
private:
    int _val;
    type _member;
};


int foo(int value)
{
	cout<<"extern int foo"<<endl;
	return 0;
}

int main()
{
	
	ScopeRules<int> b;
	b.invariant();
	b.type_dependent();
	return 0;
}

  书中提到,对于上面的例程,由于两个模板有两个作用域,一个是scope of the template declaration,这个作用域只能看到double foo(double val);二是scope of the template instantiation这个作用域能够看到两个版本的foo。按照文中提到,两个函数invarianttype_deendent中的foo应该调用哪个。非成员对象或者函数的名字决议应该是根据这个名字是否与用于具象化该模板类的参数类型相关性而决定:

  • 如果不相关,则以scope of the template declaration决定;
  • 如果相关,则以scope of the template instantiation来决定。

  但是在实际测试的结果如下:

extern double foo
extern double foo

此处甚是疑惑?也可能书籍过于老(2001年),已经和现代C++有差距。

1.4 成员函数的具象化行为

  编译器如何找出函数的定义? :两种方式:

  • 包括 template program text file,就好像它是个header文件一样;
  • 要求一个文件命名规则,比如能够要求在Point.h中发现的函数声明,其 template program text一定要放置于文件Point.c或Point.cpp中。

  编译器怎样可以仅仅具现出用到的member functions? : 两种方式:

  • 忽略这个要求,把一个已经具现出的类的全部成员函数都产生出来;
  • 仿真链接操作,检測看哪一个函数真正须要,然后仅仅为它产生实体。

  编译器怎样阻止member definition在多个.o文件里都被具现呢? : 两种方式:

  • 产生多个实体,然后从链接器中提供支持,仅仅留下当中一个实体,其余忽略;
  • 方法二是由使用者来导引"仿真链接阶段"的具现策略,决定哪些实体才是所须要的。

  

2 异常处理机制

  为了支持异常机制,需要一些额外的信息或者操作来保证异常的安全性,在程序大小和执行速度之间做选择的可能做法:

  1. 编译器可以在编译期建立起用于支持的数据结构,速度可以但是程序会膨胀;
  2. 编译器可以在执行期建立起用于支持的数据结构,程序大小适中但是速度可能慢点儿。

  当一个异常发生时,编译系统需要完成:

  1. 检验发生throw操作的函数;
  2. 觉得throw操作是否发生在try区段中;
  3. 若是,编译系统必须把异常类型拿来和每一个catch子句进行比较;
  4. 如果比较吻合,流程控制应该交到catch子句手中;
  5. 如果throw的发生并不在try区段中,或没有一个catch子句吻合,那么系统必须(a)摧毁所有已构造的局部对象,(b)从堆栈中将木目前的函数“unwind”(解除)掉。(c)进行到程序堆栈的下一个函数中去,然后重复上述步骤2~5。

  当一个异常被抛出时,异常对象会被产生出来并通常放置在相同形式的异常数据堆栈中。从throw端传给catch子句的,是异常对象的地址、类型描述器(或是一个函数指针,该函数会传回与该异常类型有关的类型描述器对象)以及可能会有的异常对象描述器。
  另外,在异常中如果通过变量是通过拷贝传播的,即在一个catch语句中对于异常对象的任何修改都是局部性的。

3 RTTI

  一个保证安全的向下转换操作必须在执行期对指针有所查询,看看它是否指向它所展现的对象的真正类型。因此,要想支持type-safe downcast,在对象空间和执行时间上都需要有一些额外负担:

  1. 需要额外的空间以存储类型信息,通常是一个指针,指向某个类型信息节点;
  2. 需要额外的时间以决定执行期的类型(run type),因为,这需要在执行期才能决定。

  对于那些展现多态特性的类C++能够提供一个安全的downcat,实际实现中实在虚函数表的-1位置添加了类的RTTI信息。
  C++提供的dynamic cast能够保证安全性,如果转换成功则指针被成功转换;如果失败则指针被设为0。另外如果通过引用的话,转换失败会抛出异常bad_cast exception来保证转换的安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值