effective c++ 笔记 (13-17)



//---------------------------15/03/30----------------------------


//#13   以对象管理资源

{

   void f()

    {

        Investment *pInv = createInvestment();

        ...

       delete pInv;

    }

/*  

    1:这里很容易出现内存泄漏:

        1>...中过早地return

        2>...中抛出异常。

    2:为了确保pInv总是被释放,我们需要将资源放进对象内,当控制流离开f时,

    这个对象会自动调用析构函数释放掉资源。

    3:这里有两个常用的智能指针可以实现:

        1>std::auto_ptr。使用方法:std::auto_ptr<Investment> pInv(createInvestment());

        记得不要让多个auto_ptr同时指向一个对象,这样会造成多次delete。为了预防这个问题,auto_ptr

        在被copy给别的指针时,会变成null

        2>tr1::shared_ptr。这个智能指针使用引用计数,在被copy一次后会自动计数加1,调用一次析构函数

        会自动计数减1,当计数为0时销毁对象。

        唯一的问题是循环引用。两个没有被使用的对象相互指着对方,这样看起来好像都在使用中,然而并不是这样。

    4:如果要对申请到的数组资源使用智能指针,可以使用boost::scoped_arrayboost::shared_array

    这两个在析构时调用delete[];

*/

}


//#14 在资源管理类中小心coping行为

{

//  auto_ptrtr1::shared_ptr不适合掌管非heap-based资源。这时候需要自己建立资源管理类

   class Lock

    {

    public:

       explicit Lock(Mutex* pm): mutexPtr(pm)

        {

            lock(mutexPtr);

        }

        ~Lock()

        {

            unlock(mutexPtr);

        }

    private:

        Mutex *mutexPtr;

    };

    

/*  这时会出现一个问题:如果对象被复制,这个锁会被再锁上一次。如果是在同一线程中这么做

    程序就死锁了,无法再恢复。所以大多数时候,可以选择以下两种处理。

        1>禁止复制。像Lock这样的class,这样处理是科学的。可以从Uncopyable那用私有继承。

        2>对底层资源使用引用计数法Mutex引用计数版本可以使用tr1::shared_ptr,并

        指定删除器,当引用次数为0时,删除器会被调用。代码看起来像这样:                */

   class Lock

    {

    public:

       explicit Lock(Mutex* pm):mutexPtr(pm, unlock)

        {

            Lock(mutexPtr.get());

        }

    private:

        std::tr1::shared_ptr<Mutex> mutexPtr;

    };

/*  有时候会有两种特殊的需求:

        1>复制底部资源(深拷贝)。这是为了当你不需要某个复件时确保它被释放,所以拷贝时要连同

        指针指向的资源一起拷贝,不然原始对象删除后就没法用了。

        2>转移底部资源的拥有权。就像auto_ptr一样。

    

*/

}


//#15:  在资源管理类中提供对原始资源的访问

{

/*  1:在资源管理类中,常常需要提供对原始资源的访问,原因是:一些api需要的就是原始资源

    如果传入一个资源管理类,它们并不起作用。

    2:提供对原始资源访问的方法有二:

        1>提供显示访问:                                                   */

   class Font

    {

    public:

       explicit Font(FontHandle fh): f(fh)

        {}

        ~Font() { releaseFont(f);}

        FontHandle get()const {return f;}

    private:

        FontHandle f;

    };

/*      这样一来就可以通过调用get()来访问原始资源了,但是每次都要用f.get(),显得十分麻烦。

        2>提供隐式转换函数:

        operator FontHandle() const {return f;}

        但是这么做,存在风险,客户有时候笔误就会造成获得一个错误的类型:                */

    Font f1(getFont());

    ...

    FontHandle f2 = f1;//本来客户是想要 Font f2 = f1;

/*      但是就算这么调用了,客户也看不出来,因为隐式转换的关系,表达式是可以编译通过的。

        而且使用起来也是一样的。最后,当f1被销毁时,问题来了,f2就成为虚吊的,就是指向

        的资源以及被释放。

    3:两种设计各有优势,为了安全倾向显示转换,为了方便倾向隐式转换。

    4:RAII class返回原始资源与封装发生矛盾,这是真的。但是并没有关系,RAIIclass是为了确保

    资源一定会被释放,而不是为了封装。

*/

}

 

//#16  成对使用newdelete时要采用相同形式

{

/*  1:当使用new时,有两件事情发生:

        1>内存被分配出来。

        2>针对此内存会调用构造函数(一个或多个)

    2:当使用delete时,也有两件事发生:

        1>先调用析构函数(一个或多个)

        2>释放内存。

    3:数组对象的内存中一般还包含一个数组大小的记录,以便delete知道需要调用多少次析构函数

    所以在调用时必须成对出现,不然会发生不可预测的情况。

    4:使用typedef时,最好不要对数组进行typedef

    比如: typedef std::string AddressLines[4];

    这样很容易错误地使用delete,所以应该避免这种定义。                            */

}

 

//#17   以独立的语句将newed对象置入智能指针

{

//  看一个函数调用:

    processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());


/*  这样调用有可能造成内存泄漏,首先,编译器为了效率问题执行以下顺序:

        1>new widget;

        2>priority();

        3>tr1::shared_ptr的构造函数

    如果priority()的调用导致异常,那么new Widget返回的指针就遗失了,内存就泄漏了。

    所以为了避免着问题很简单:                                               */

    std::tr1::shared_ptr<Widget> pw(new Widget);

    

    processWidget(pw, priority());

//  newed对象置入智能指针的操作用独立语句来实现,这样就不会产生内存泄漏了。

}








原文地址:https://www.cnblogs.com/boydfd/p/4983152.html