zoukankan      html  css  js  c++  java
  • 可爆炸性析构函数

    可爆炸性析构函数

    日期:2004/02/28– 2007/01/07

    本文首次刊登于《游戏创造》,现开放与大家共享,转载请注明出处。
    下载地址

    作者介绍

                唐亮(千里马肝),四年游戏从业经验,曾任职于大宇软星科技(上海)有限公司任程序技术指导,现在ATI任职Engineer,主要负责ATI CrossFireXP/Vista上的开发和维护。迄今为止主要个人作品为《阿猫阿狗2》,参与开发《汉朝与罗马》、《阿猫阿狗大作战OLG》和《仙剑奇侠传4》,主要研究方向为C++、图形渲染技术和系统架构。

    blog地址:http://oiramario.cnblogs.com

     

     

    开场白

    本文是鄙人在2004年有感而发写下的一篇文章,当时受到他人讨论的启发,不由得兴从心头起,code从手中生。文中所介绍的技法,可能以今天的眼光来看,尚有不足之处,不过它提出了一种比较新奇的方法来解决问题,我想其思想本身才是需要注意的重点。C++是一个灵活自如的语言,身为C++饭,吾辈有责任将她发扬光大,我相信还有更多有趣的技巧,有待大家一起挖掘,希望本文能起到抛砖引玉的作用,谢谢。

     

    前言

    任何语言,任何程序,都会有“操作失败”的情况发生。在C语言这种结构化编程语言中,处理这种情况的方法就是通过返回值来表现。在我们的游戏中,往往会因为这样那样的不小心,存在着成百上千的bug,有时候真是“修不完理还乱”,甚至有的游戏在不得已的情况下,还将bug遗留到发行后再通过patch的方式来进行弥补。可以见得,在没有一个好的方法来避免bug产生的时候,我们面对bug是多么得无奈。我们应该如何避免它,以及如何通过一个好的方法来捕捉它呢?

    这实在是一个全方位的问题,例如前期进行仔细的项目分析,中期保持清晰的逻辑从而编写强壮的代码,后期通过详尽的log信息来回溯事故现场,对大量异常条件的处理,使用大量的Assert对不正确参数的断言。所有的这些方法,都是想在调试期就将问题尽量全部得排除,把bug们扼杀在摇篮里。

    但是这毕竟是理想的情况,人非圣贤孰能无过?谁也不能保证自己永远处于逻辑亢奋状态,而纯理论往往讨论的是一种“乌托邦”的理想国度,但是我们不是学院派,期望能出现一种切合实际的解决方案。所以,当意识到人的惰性的必然性,那么就需要产生出一种制度来进行控管和规避。那么接下来,我将会介绍了一种新的方法,它叫作:可爆炸性析构函数

     

    现场

    通常我们会将某函数设计成:在操作成功的情况下返回0;当遇到错误发生时,可能会用1代表内存分配失败,用2代表文件打开失败,用3代表无法找到设备等等。虽然这样看起来很美,但是实际执行下来,通常会出现以下的几个问题:

     

    1.       要求同步维护文档,说明各个返回值所代表的意义。

    2.       在某些情况下可能需要修改其返回值所代表的意义,例如将返回值1从内存分配失败改成代表成功,这样一来,函数使用者的代码就需要修改。

    3.       最重要的是,使用者完全可以不检查返回值。这样一来,如果接下来的代码依赖于该函数必须正确执行完成(通常我们都这样假设),一旦发生错误,按照顺序执行的流程,下面的代码照样会执行,从而一错再错,变成破罐子破摔。以我多年Debug的经验,通常最不好修的bug都是由此而引起的。

     

    所以,在面向对象的语言中,如DELPHIC++,都引入了“异常”这种概念来处理错误。当出现错误时,实现者可以选择抛出异常,这意味着如果调用者不对该异常进行处理,该异常将会按照函数调用堆栈一级级地向上抛出,直到找到对应的处理模块,如果一直抛到最外层的main函数都无法找到,则程序会立即中止。

    但是以C++为例,为了处理异常,C++需要维护像是函数调用堆栈等一类的东西,这样会对程序的执行效率和空间上带来开销。对比异常所带来的好处,一般的程序大都可以忽略这种开销;但是像是一些对效率和内存空间要求很高的,如嵌入式或驱动级的程序,通常在这类程序的Coding Standard里就直接被声明为不被允许使用。所以,这时又不得不退回来重新使用返回值来处理错误。

    那么我们应该怎么办呢?考虑到使用者完全可以忽略返回值的问题,于是就有了接下来的方法。

     

    分析

                我们的口号是:强迫使用者必须检查函数的返回值,如果返回值不被检查的话,将会在运行期弹出错误以警告使用者。那么首先概略得设计一下,就是将会有一个bool变量 ,暂且称作为checked,将会在返回值构造时初始为false,只有返回值被使用者检查了,才会被置为true,然后在返回值析构时会判断checked变量是否为true,否则将立即报错。

                当然,实现方法是将这种概念用“类”来表现,使用者在使用该类(以下统一称作类型T)作为函数返回值时,假设原本是以int作为返回值,则该类所表现的行为和操作,应该与int“完全一致”。

                而“检查”的概念,我认为在语言表达中,即是:

                T::operator == ()

                T::operator != ()

                T::operator int ()

                为了保证“传递性”,当T的实例x作为返回值返回时,有以下二条应该被遵守:

    1.       如果x不被检查,则x会在析构时报错

    2.       y=x时,x会被认为已经将“责任”传递给了yx解除责任,而y则有义务同上

     

    整理

    1.       因为T将用来代替int,则T应有operator int()

    2.       为了与int的行为保持一致性,T应该重载operator ==operator !=

    3.       为了支持所有返回值的类型,所以T被实现为一个template

    4.       为了只在DEBUG期进行,避免RELEASE期的开销,则有

    enum ErrType
    {
               Success,
               Fail
    };

    #ifdef CHECK_RESULT
               typedef InspectResult<>                          ResultInt;
               typedef InspectResult<ErrType>  ResultEnum;
    #else
               typedef int                     ResultInt;
               typedef ErrType ResultEnum;
    #endif

    ResultInt Func1()
    {
               ResultInt ret = 1;
               return ret;
    }

    ResultEnum Func2()
    {
               ResultEnum ret = Success;
               return ret;
    }

    5.       如果返回值是一个“大的类型”如string,为避免临时变量产生导致开销,以及不同的调用方式和习惯的支持,则有const string &result()const

     

    实现

    template <typename ResultType=int>

    class InspectResult

    {

                mutable bool      _checked;          // 检查标志

                ResultType                     _ret;                  // 返回值

     

    public:

                /*-------------------------------------------------------------

                            构造函数

                -------------------------------------------------------------*/

                InspectResult(const ResultType &ret)

                : _checked(false), _ret(ret)

                {

                }

     

                /*-------------------------------------------------------------

                            拷贝构造函数

                -------------------------------------------------------------*/

                InspectResult(const InspectResult &rhs)

                : _checked(rhs._checked), _ret(rhs._ret)

                {

                            // rhs"被检查权"传递给this(下同)

                            rhs._checked = true;

                }

     

                /*-------------------------------------------------------------

                            析构函数

                -------------------------------------------------------------*/

                ~InspectResult()

                {

                            // 如果没有检查过返回值则报错

                            assert(_checked);

                }

     

                /*-------------------------------------------------------------

                            operator =

                -------------------------------------------------------------*/

                InspectResult & operator = (const InspectResult &rhs)

                {

                            _checked = rhs._checked;

                            _ret = rhs._ret;

     

                            rhs._checked = true;

                           

                            return *this;

                }

     

                /*-------------------------------------------------------------

                            重载operator = (const ResultType &ret)

                            以支持InspectResultResultType之间的直接操作

                            因为ctornon-explicit, 以避免临时变量的产生

                -------------------------------------------------------------*/

                InspectResult & operator = (const ResultType &ret)

                {

                            _checked = false;

                            _ret = ret;

                           

                            return *this;

                }

     

                /*-------------------------------------------------------------

                            所谓"返回值必须检查", 在此我视为operator ==动作

                            注意这里因为值已被检查, 所以thisrhs都将视为已检查

                -------------------------------------------------------------*/

                bool operator == (const InspectResult &rhs)const

                {

                            _checked = rhs._checked = true;

     

                            return _ret == rhs._ret;

                }

     

                /*-------------------------------------------------------------

                            所谓"返回值必须检查", 在此我视为operator ==动作

                            注意这里因为值已被检查, 所以_checked = True

                -------------------------------------------------------------*/

                bool operator == (const ResultType &ret)const

                {

                            _checked = true;

     

                            return _ret == ret;

                }

     

                /*-------------------------------------------------------------

                            operator != (const InspectResult &rhs)const

                -------------------------------------------------------------*/

                bool operator != (const InspectResult &rhs)const

                {

                            return !(*this == rhs);

                }

     

                /*-------------------------------------------------------------

                            operator != (const ResultType &ret)const

                -------------------------------------------------------------*/

                bool operator != (const ResultType &ret)const

                {

                            return !(*this == ret);

                }

     

                /*-------------------------------------------------------------

                            operator ResultType

                -------------------------------------------------------------*/

                operator ResultType ()const

                {

                            _checked = true;

     

                            return _ret;

                }

     

                /*-------------------------------------------------------------

                            如果ResultType是一个大的class(string)

                            这时使用operator ResultType会有临时变量的开销

                            但若ResultType是内建的类型(char), 则不建议使用本函数

                -------------------------------------------------------------*/

                const ResultType &result()const

                {

                            _checked = true;

     

                            return _ret;

                }

    };

     

     

    enum ErrType

    {

                Success,

                Fail

    };

     

    #define CHECK_RESULT

     

    #ifdef CHECK_RESULT

                typedef InspectResult<>                          ResultInt;

                typedef InspectResult<ErrType>  ResultEnum;

    #else

                typedef int                     ResultInt;

                typedef ErrType ResultEnum;

    #endif

     

    结论

    1.         矫枉不必过正,只需要对于那些“必须成功执行”或“必须对执行中产生的错误进行处理”的函数使用上面所介绍的方法。

    2.         语言是表达思想的一种工具,利用C++的特点(支持运算符的重载)。我们可以实现一些在基本语言层面上无法表现的东西。

    3.         通过template,我们可以用泛型实现对所有类型的支持。

    4.         因为检查会有开销(至少会多出一个bool,内存对齐的情况下类型T会膨胀),通过define,我们可以在需要的时候作检查,不需要的时候则消除开销。

    5.         为了贯穿思想,实现出来的东西往往不像想的时候那么简单,需要考虑很多方面。总之,思想是最重要的东西。

     

                :本文及代码,启发自《程序员》20029月中的《C++ Exception》专栏讨论,其中myan(孟岩)在与某老外通信中谈到此名词:“可爆炸性析构函数”,希望能给你带来启发或是帮助。

     

                代码下载地址:http://oiramario.cnblogs.com/std56.rar

     

  • 相关阅读:
    转:Visio之取消自动对齐与粘附
    转:Excel怎样修改图例名称
    一张图说明学习率和loss之间的关系
    转:loss不收敛或不下降问题处理经验
    N-UCLA骨架数据可视化
    转:IEEE论文投稿流程(格式说明,新手指南,模板)
    Ubuntu下无法安装sun-java6-jdk的解决办法
    Git 学习笔记一
    轮播特效小项目总结
    第9次作业--接口及接口回调
  • 原文地址:https://www.cnblogs.com/oiramario/p/840814.html
Copyright © 2011-2022 走看看