可爆炸性析构函数

可爆炸性析构函数

日期: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

 

原文地址:https://www.cnblogs.com/oiramario/p/840814.html