Effective C++ 笔记 第三部分 资源管理

来源:互联网 发布:锦绣未央网络上映 编辑:程序博客网 时间:2024/06/05 10:26

资源管理就是一旦用了它,将来必须还给系统。


13.以对象管理资源(Use objects to manage resources)


为防止资源泄露,请使用RAII(资源取得时机便是初始化时机)对象,他们在构造函数中获得资源并在析构函数中释放资源。
两个常被使用的RAII classes分别是shared_ptr和auto_ptr。前者通常是较佳的选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null。

有时候并不是有delete语句就不会造成内存泄露,如下面这些情况,还有很多类似的情况会造成内存泄露。

class A{public:    ~A(){        printf("A DEL\n");    }    int val;};void fun(){    A* a = new A();    if (...) {        return;//delete a没有调用,造成内存泄露    }    auto someObject = new SomeClass();//若此处抛出异常则delete a没有调用,造成内存泄露    delete a;}

解决方案一:std::auto_ptr
auto_ptr<>在离开作用域时会自动调用其指向对象的析构函数,auto_ptr<>管理资源的底层条件是必须绝对没有一个以上的auto_ptr同时指向一个对象,因为在离开作用域时会调用多次析构函数,造成未定义结果。同时对auto_ptr进行copy时,被copy的auto_ptr指针会清零。

void fun(){    std::auto_ptr<A> a(new A());    auto b = a;    printf("%d",a->var);//ERROE:a是null}

方案二: std::shared_ptr<>
使用shared_ptr需要包含”memory”头文件,详情参考C++ primer中文第5版400页。
shared_ptr使用类此Objective-C的引用计数机制,即有一个指向该对象的指针,其引用计数就+1,有一个指向他的指针不在指向他了则引用计数-1.当引用计数为0时调用其析构函数。
但shared_ptr有一个弱点,就是无法识别环形引用,即两个没有用的对象互相指向彼此。

class A{public:    ~A(){        printf("A DEL\n");    }    int var = 1;};void fun(){    std::shared_ptr<A> a(new A());    std::shared_ptr<A> b(a);    printf("a->val = %d, b->val = %d\n",a->var,b->var);    //输出:    //a->val = 1, b->val = 1    //A DEL}

shared_ptr和auto_ptr在析构函数内做delete操作而不是delete[],意味着动态分配的array上不宜使用这两种指针,建议改用vector代替。


14.在资源管理类中小心copying行为(Think carefully abot copying behavior in resourec-managing classes)


复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
普遍常见的RAII class copying行为是:抑制 copying、施行引用计数法。不过其他行为也都是可能被实现。

当一个RAII对象被复制时会发生什么事情:
1.禁止复制:
方法,参考6.
2.对底层资源使用引用计数
使用shared_ptr<>,但有些时候我们并不希望当引用计数为0时删除其所指物,例如使用一个互斥锁Mutex管理类(在构造函数中加锁,在析构函数中解锁),当引用计数为0时希望解锁而非删除,shared_ptr<>也支持这样的操作。

#include <iostream>#include "memory"class A{public:    ~A(){        printf("A DEL\n");    }    int var = 1;    static void foo(A* A){//foo参数需包含一个对象,并为static        printf("foo\n");    }};void fun(){    std::shared_ptr<A> a(new A(),A::foo);    std::shared_ptr<A> b(a);    printf("a->val = %d, b->val = %d\n",a->var,b->var);    //输出:    //a->val = 1, b->val = 1    //foo    //并未调用A的析构函数}int main(int argc, const char * argv[]) {    fun();    return 0;}

15.在资源管理类中提供对原始资源的访问(Provide access to raw resources in resource-managing classes.)


APIs往往要求访问原始资源,所以每一个PAII class应该提供一个”取得其所管理之资源”的办法。
对原始资源的访问可能经由显示转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。

我们在资源管理类中对资源进行管理,而客户需要获得资源时,我们需要经过一些转换。

class A{//资源管理类public:    int var = 1;};int getIntData(A* a){    return a->var;}void fun(){    std::shared_ptr<A> a(new A());//13中,我们认为应该使用shared_ptr指针    getIntData(a);//ERROR:此处应传入A*类型,而非std::shared_ptr<A>类型}

所以我们应在资源管理类中提供可将RAII class对象转换为其所含原始资源的函数。

显示转换:

std::shared_ptr<>提供了转换函数get(),可直接使用,转换为原始指针。

 void fun(){    std::shared_ptr<A> a(new A());    getIntData(a.get());//使用get(),获得A*对象}

或者使用访问操作符:

void fun(){    std::shared_ptr<A> a(new A());    int intData = a->var;}

隐式转换:

有事频繁的使用显示转换会使客户很烦躁,所以可以提供隐式转换

class A{public:    operator int() const{        return var;    }private:    int var = 1;};void fun(){    A a;    int intData = a;//发生了隐式转换,A转换为int    printf("%d",intData);//输出1}

16.成对使用new和delete时要采取相同的形式(Use the same from in corresponding uses of new and delete)


如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。


17.以独立语句将newed对象置入智能指针(Store newed objects in smart pointers in standalone statements.)


以独立语句将newed对象存储(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源漏洞。

#include <iostream>#include "memory"class A{public:    int valData;};int getData(){    int val = 0;    //处理val    //此处可能发生异常    return val;}void fun(std::shared_ptr<A> a,int data){    //do something}int main(int argc, const char * argv[]) {    fun(std::shared_ptr<A> ( new A() ), getData());//若getData()发生异常,被传入的指针可能并未shared_ptr包装。    return 0;}

如fun的定义所示,fun接受一个shared_ptr包装的资源管理类A的指针,和一个int类型的data。
如main函数中调用fun函数的方式,在fun被调用前需要做三件事
1.执行getData();
2.执行new A();
3.执行std::shared_ptr构造函数;
然而上述三件事情的执行顺序并不是确定的,因为C++并未对此做出要求。我们只知道3是要比2后执行的,所以执行顺序有可能是2->1->3;这时就可能会出现内存泄露,因为如果getData()发生了异常,那么3将不被执行,所以传入的A类型对象可能并未被shared_ptr所包装。导致了内存泄露。所以上面例子中的写法并不可取,应该把newed对象以独立语句(置入)智能指针内。

int main(int argc, const char * argv[]) {    std::shared_ptr<A> a(new A());//把newed对象以独立语句(置入)智能指针内。    fun(a, getData());//这样不会导致潜在的内存泄露危机。    return 0;}
0 0
原创粉丝点击