草庐IT

c++对象模型 模板 异常处理 执行期类型识别

chenglixue 2023-04-19 原文

template

声明

​ 当我们声明一个template class、template class memberfunction等,会发生何事?

​ 现有如下片段:

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,在程序中编译器对其并没有任何反应。换句话说,上述的data member其实并不可用

实例化

  • 我们需要显示地指定类型才可使用data member:
Point::Status s;

Point<float>::freeList;

//如下会产生第二个freeList实例
Point<double>::freeList;

//定义指针指向特定实例,程序中啥也没发生。因为编译器不需要知道与该class有关的任何member数据,也没必要初始化template实例,且指针可以为0
Point< float >* ptr = 0;

//reference则不同,它需要实例化,因为reference是需要绑定对象的
const Point<float>& ref = 0;

//扩展
Point<float> temp( (float) 0 );
const Point<float>& ref = temp;

//导致实例化
const Point<float> origin;

​ 对于int和long相同的架构中,以下两个实例化c++standard并未对此进行强制规定应实例化一个还是两,不过大部分编译器都是实例化两个:

Point<int> pi;
Point<long> pi;
  • 目前的编译器,面对一个template的处理是完全解析但不做类型检验;只有在实例化操作发生时才做类型检验
  • template class内的member functions只有在被使用的时候,才会被实例化

名称决议

  • 在c++standard规定了template两个不一样的端,分别是定义template(template定义的文件)的程序端和实例化template(使用的特定例子)的程序端

    //定义template端
    //只有一个foo()声明位于定义端内
    extern double foo( double );
    
    template< class type >
    class A
    {
    public:
        void do1()
        {
            _member = foo( _val );
        }
        
        type do2()
        {
            return foo( _member );
        }
        //...
        
    private:
        int _val;
        type _member;
    }
    
    //实例化端
    //两个foo()声明在实例化端内
    extern int foo( int );
    
    template< class type >
    class A
    {
    public:
        void do1()
        {
            _member = foo( _val );
        }
        
        type do2()
        {
            return foo( _member );
        }
        //...
        
    private:
        int _val;
        type _member;
    }
    
    A<int> a;
    
    //应该调用extern double foo( double )。因为_val的类型与template type参数类型无关
    a.do1();
    
    //应调用extern int foo( int )。因为_member与template type参数类型有关
    a.do2();
    
  • template中,对一个nonmenber name的决议结果是根据此name的使用是否与"用以实例化该template的参数类型"有关来决定:

    • 若其使用互不相关,则使用定义端来决定name
    • 若其使用有关联,则使用实例化端来决定name
  • 编译器必须保持两个端上下文(scope contexts):

    • 定义端。专注一般的template class
    • 实例化端。专注特定实例

异常处理

  • 想要支持异常处理,编译器的主要工作是找出catch子句以处理被抛出来的exception

  • 异常处理由三个主要组件构成:

    1. 一个throw子句。它再程序某处发出一个exception,被抛出的exception可为内建类型,亦可为自定类型
    2. 一个或多个catch子句。每个catch子句都为一个exception handler。表示此子句准备处理某种类型的exception,且在封闭的大括号区段中提供实际的处理程序
    3. 一个try区段。他被围绕一系列叙述句,这些叙述句可能会引发catch子句起作用
  • 当一个exception被抛出,控制权会从函数调用中被释放出,并寻找一个吻合的catch子句。若没有则调用默认处理例程terminate()。当控制权被释放,堆栈中每个函数调用也就被推离,被推离前函数的local class objects的destructor会被调用

    Point* mumble
    {
        //区域一
        Point* pt1, * pt2;
        //若一个exception于此被抛出,mumble()会被推出堆栈
        pt1 = foo();	
        if( !pt1 ) return 0;
        
        //区域二
        Point p;
        //若一个exception于此被抛出,在推出mumble()前需要先调用p的destructor
        pt2 = foo();
        if( !pt2 ) return pt1;
    }
    
    • 支持以上exception handling,编译器有两种策略:
      1. 把两个区域(上面两个exception)以个别的"将被摧毁的local objects"链表(在编译器准备妥当)联合起来
      2. 让两个区域共享同一个链表,该链表在执行期扩大或缩小
    • 一个函数可以分为多个区段:
      1. try以外的区域,且没有active local objects
      2. try以外的区域,但有一个以上的active local objects需要析构
      3. try以内的区域

​ 现有一函数含有对一块共享内存的locking和unlocking操作,此时异常处理不保证能正确运行:

void mumble( void* arena )
{
    Point* p = new Point;
    smLock( arena );
    
    //若此处有exception被抛出,会产生问题
    //...
    
    smUnLock( arena );
    delete p;
}

//因此我们需要安插default catch子句
void mumble( void* arena )
{
    Point* p = new Point;
    smLock( arena );
    
    try
    {
        smLock( arena );
    }
    catch(...)
    {
        smUnLock( arena );
        delete p;
        throw;
    }
    
    smUnLock( arena );
    delete p;
}
  • 处理资源管理,一个办法是将资源需求封装于class object内,并由destructor释放资源

​ 当一个exception发生时,编译系统必须完成以下:

  1. 检验发生throw操作的函数
  2. 决定throw操作是否发生在try区段
  3. 若是,编译系统必须把exception type拿来和每个catch子句比较
  4. 若比较后吻合,流程控制交给catch子句
  5. 若throw的发生不在try区段,或没有一个catch子句吻合,那么系统必须摧毁所有active local objects,从堆栈中将目前的函数推离,到下一个函数。再重复2-5
  • 编译器必须标示出之前所提到的多个区段,并使它们对执行期的异常处理有所作用。有一个策略是构造program counter-range表格:

    program counter内含下一个即将执行的程序指令。为了在一个内含try区段的函数中标示出某区域,可以把program counter的起始值和结束值存储在范围表格

    当throw发生时,目前的program counter值拿来与对应的范围表格进行对比,以决定目前作用中的区域是否在一个try区段内。若是,则需找出相关catch子句;若无法处理或exception再次被抛出,目前此函数会从堆栈中被推出,而program counter会被设定为调用端地址,循环将再度开始

  • 对于被抛出的exception,编译器必须产生一个类型描述器,对exception的类型进行编码,若为一个derived type,编码内容必须包含其所有base class类型信息(可能被member function捕捉)。编译器还必须为每一个catch子句产生一个类型描述器。执行期的handler会将"被抛出的object的类型描述器"和"每一个catch子句的类型描述器"进行比较,直到吻合或是堆栈已被推离

执行期类型识别

  • downcast有效地把一个base class转换为继承结构的derived class,但其有潜在的危险,因为它遏制了类型系统的作用

  • 一个type-safe downcast必须在执行期对指针查询,看其是否指向它所表达的object的真正类型。想要支持type-safe downcast,需要以下要求:

    1. 需要额外的空间存储类型信息,通常为指针
    2. 需要额外的时间来决定执行期的类型
  • c++的RTTI机制提供安全的downcast设备,但只对多态类型有效

  • dynamic_cast可以在执行期决定真正的类型

  • type-safe dynamic_cast:

    • 若downcast为安全的,则会传回被适当转换过的指针;若不安全,则传回0
    • 对一个class指针施行dynamic_cast,会获得true或flase
      1. 若传回真正的地址,则表示这一object的动态类型被确定,一些与类型有关的操作可以施行于此
      2. 若传回0,则表示没有指向任何object,应该以另一种逻辑施行于未确定的object
    • 对于reference dynamic_cast同样适用,但若为non-type-safe cast,结果和指针不同。因为reference不可以把自己设为0。因此会有另一套方案:
      1. 若reference真正参考到适当的derived class,downcast被执行
      2. 若reference并不真正是某种derived class,由于不能传回0,则抛出bad_cast exception
  • dynamic_cast的成本:编译器产出类型描述器

    //fct为type派生
    typedef type* ptype;
    typedef fct* pfct;
    
    do( ptype pt )
    {
        if( pfct pf = dynamic_cast<pfct>(pt) )
        {
            //...
        }
        
        //转换
        //virtual table第一个slot内含type_info object地址
        ((type_info*)(pt->vptr[0]))->_type_descriptor
    
    }
    

有关c++对象模型 模板 异常处理 执行期类型识别的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  4. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  5. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  6. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  7. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  8. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  9. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  10. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

随机推荐