浅谈C++中的资料经管

    添加时间:2013-5-5 点击量:

    C++的错杂是一个根蒂根基领实,这也成了很多人对C++横加责备的原因。事实上,正如陈皓在“C++的数组不支撑多态”?这篇文章中提到的,很多人在并不真正懂得C++的景象下,就喜好得出如许的结论。更有甚者,把C说话本身的“坑”也归结为C++的题目。如许的人实在不少,C++11作为具有争议的说话之一,每一次评论辩论到涉及说话选择的时辰,都邑引起一场“决战苦战”,但成果往往不了了之,喜好C++的持续死守C++阵营,憎恶C++的把精力留到下次黑C++的时辰。对于客观公道批驳C++的,我内心尊钦服气;而对于还没搞清楚C++就信口开河的,我默示鄙夷。任何一门说话都有本身的汗青靠山和定位,C++被设计成如许,从汗青上来看,是为了兼容C,使得C法度可以不消批改就可以持续应用;从定位上看,就是三大束缚:与C的完全兼容、静态类型搜检、高机能。我至心的,若是有人不喜好C++,在搞懂它之后再黑,免得误人后辈。


    每次写C++的长处之前,都想好好发泄一下,好了,言归正传。这篇文章想商量下C++中的资料经管,谈到资料经管,就不得不谈异常安然,恰是因为有了异常,才使得资料经管变得加倍首要。C++11供给了一套很是好的编程Idom来处理惩罚这个题目,C++11的新特点使得这些Idom变得加倍易用。


    策画中的资料是个很是广泛的概念,内存、锁、文件、Socket等等都是资料,C++中可以经由过程同一的体式格式经管这些资料,即RAII(拜见The C++ Programming Lauguage, Special Edition, p364, 14.4节)。其根蒂根基思路很是简单,用类来封装资料,在类的机关函数中获取资料,在类的析构函数中开释资料。应用的时辰,把这个类在栈上方实例化出一个对象,当这个对象超出感化域时,这个对象的析构函数会被调用,从而开释资料。恰是这个简单的体式格式,构成了C++资料经管的根蒂根基,并且如许的体式格式是异常安然的,因为:1、若是在对象机关之前产生异常,则资料还没申请,不会有题目;2、若是在类的机关函数中产生异常,C++编译器包管资料不会产生资料泄漏(拜见Exceptional C++, p26, Item8);3、在对象机关好之后产生了异常,stack unwinding(拜见The C++ Programming Lauguage, Special Edition, p355, 14.1节)的过程中,C++标准请求编译器包管当前栈上方成功机关的对象的析构函数必然会获得调用,内存必然获得开释。


    智能指针就是RAII的实现典范,专门用来经管内存,C++11中有三个智能指针:unique_ptr、shared_ptr和weak_ptr。auto_ptr已经是过期的了,它的功能被unique_ptr庖代了,后者可以用于STL容器。


    对于其它资料,须要用户本身去封装,同样的资料只要封装一次,今后应用起来就便利了。若是嫌每个资料都要用类包装起来麻烦,可以哄骗ScopeGuard来处理惩罚,这个举措措施由Andrei Alexandrescu发明,刘未鹏在C++11(及现代C++风格)和快速迭代式开辟中做了具体介绍。当然了,ScopeGuard在C++11(得益于std::function和lambda表达式)下,才达到了很是易用的境界。我这边贴一下SocpeGuard的代码,有爱好的可以参考刘未鹏的文章。



    class ScopeGuard
    
    {
    public:
    explicit ScopeGuard(std::function<void()> onExitScope)
    : onExitScope_(onExitScope), dismissed_(
    false
    { }

    ~ScopeGuard()
    {
    if(!dismissed_)
    {
    onExitScope_();
    }
    }

    void Dismiss()
    {
    dismissed_
    = true;
    }

    private:
    std::function
    <void()> onExitScope_;
    bool dismissed_;

    private: // noncopyable
    ScopeGuard(ScopeGuard const&);
    ScopeGuard
    & operator=(ScopeGuard const&);
    };


    应用起来很简单,在以C的体式格式申请一个资料的时辰:



    HANDLE h = CreateFile(...);
    
    ScopeGuard onExit([
    &] { CloseHandle(h); });


    如许申请资料和开释资料永远写在一路,不会忘怀,并且包管资料永远不会泄漏。


    说到资料泄漏,当然想到内存泄漏了,Visual C++中可以如许的体式格式来检测内存泄漏:


    在cpp文件开端处,添加这么一段代码:



    #ifdef _DEBUG
    
    #define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__)
    #else
    #define DEBUG_CLIENTBLOCK
    #endif // _DEBUG
    #define _CRTDBG_MAP_ALLOC
    #include
    <stdlib.h>
    #include
    <crtdbg.h>
    #ifdef _DEBUG
    #define new DEBUG_CLIENTBLOCK
    #endif // _DEBUG


    在某个函数中,添加这么一个函数调用:



    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);


    如许一来,在法度在Debug模式运行停止后,若是有内存泄漏的话,会打印出类似如下的检测信息。



    Detected memory leaks!
    
    Dumping objects
    ->
    {
    197} normal block at 0 x00FBCE9844 bytes long.
    Data:
    < > 0A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    Object dump complete.


    谈了C++资料经管的益处,来吐槽下Java。Java是个喜好喊标语的说话,“一次编译,处处运行(Write once,run anywhere) ”,“法度员再也不消愁闷内存泄漏了”,“纯粹的面向对象说话”,诸如此类。先谈谈它的资料经管。Java中对资料的经管分为两种,一是内存,经由过程gc经管;二是其它资料,经由过程try、catch和finally来经管。第一个题目,不同一,Java里面不同一的处所还有很多,比如根蒂根基数据类型不克不及作为模板参数、明明是“纯粹的面向对象说话”却还有根蒂根基数据类型等等。第二个题目,try、catch和finally语法太丑了,并且最不爽的是在finally里面还要在try一下,太蛋疼了!第三个题目,内存被JVM经管了,导致gc机会不断定,严重影响Java的普及局限(在一个大型游戏中,正打boss呢,忽然gc了,然后,然后就没有然后了)。我承认,Java是个很好用的说话,能很大程度进步法度员的开辟效力,希罕是JavaEE体系,的确很成熟。我本身也写过不少Java代码,最便利的处所是失足了今后,异常栈可以或许快速帮助我定位到bug。然则Java因为樊篱了太多底层信息,导致它培养了一多量不太合格的法度员。这里不是说Java法度员不优良,只是若是一向用Java,不去懂得C/C++的话,真的很轻易退化。

    我俩之间有着强烈的吸引力。短短几个小时后,我俩已经明白:我们的心是一个整体的两半,我俩的心灵是孪生兄妹,是知己。她让我感到更有活力,更完美,更幸福。即使她不在我身边,我依然还是感到幸福,因为她总是以这样或者那样的方式出现在我心头。——恩里克·巴里奥斯《爱的文明》
    分享到: