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

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


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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值