资源管理

        在C++中,最常使用的就是动态分配内存,当我们在程序中分配出一块内存时,最终就要释放掉它,否则会造成内存泄漏。在一些大型项目中,在稳定性测试中,功能正常的程序,也许在跑一段时间或很久之后出现死机、异常重启等莫名错误,其实有可能就是内存泄漏引起的问题。内存泄漏十分难以查找,有时,我们在一个类中声明了一块空间,由于复杂的处理过程,忘记或者由于某些程序的异常跳转,导致空间最终没有释放,最终造成程序每次不断地创建新空间而不释放,出现问题。
        假如,有一个程序代码如下,在Investment类的构造函数中分配了内存空间,且我们无法直接拿到该类的构造函数,这种写法在很多项目中都很常见,尤其是组件化接口中,为了一个组件在不同项目中有不同的实现方式,构造函数基本都单例化并工厂化。这就需要我们在使用时,调用创建对象的函数创建对象,并手动的调用删除器函数释放创建对象时分配的内存,一般来说,我们不会忘记这样做,但是,假如在创建对象后,程序又做了很多其他业务流程,这些业务流程中,有一些流程出现了异常,导致程序过早发生跳转,如遇到调用某个获取状态的函数发生错误,提前return,这样,这部分构造函数申请的内存就无法释放了,从而造成内存泄漏。

include <iostream>
using namespace std;
class Investment
{
public:
    static Investment* createInvestment()
    {
        return new Investment();
    }

    ~Investment()
    {
        if(space != NULL)
        {
            delete space;
            space = NULL;
        }
        cout << "delete space" << endl;
    }

private:
    Investment()
    {
        space = new char[2048];
        cout << "new space" << endl;
    }


private:
    char* space;
};

int main()
{
    Investment* pInv = Investment::createInvestment();

	//......进行其他代码的执行
	
    delete pInv;

    return 0;
}
//运行结果:
new space
delete space

        在这里插入代码片这种情况下,可以使用C++提供的智能指针,auto_ptr和shared_ptr进行资源管理的任务。auto_ptr和shared_ptr内部使用引用计数机制,每当使用它们创建一个对象时,引用计数加1,每当不再使用时,引用计数将自动减1,引用计数为0时,将自动调用所创建对象的析构函数进行析构,从而保证申请的内存最终都可以被释放,不存在内存泄漏风险。

int main()
{
    auto_ptr<Investment> pInv(Investment::createInvestment());
    //...其他程序代码执行

    return 0;
}
//运行结果:
new space
delete space

        shared_ptr使用方法与auto_ptr一样:

int main()
{
    shared_ptr<Investment> pInv(Investment::createInvestment());
    //...其他程序代码执行

    return 0;
}
//运行结果:
new space
delete space

        auto_ptr和shared_ptr的使用,可以使我们在获得资源后立刻就放进管理对象中,管理对象运用析构函数确保资源能够被正确释放。auto_ptr和shared_ptr在正常的使用上是没有区别的,但是在涉及拷贝的过程中,这两者就有一定的区别,auto_ptr如果通过拷贝构造函数或赋值号复制时,原指针会变为null,复制所得的指针会获得资源的所有权,shared_ptr在拷贝时,不会变为null,被复制的对象也指向同一个地址,如下所示:

int main()
{
    auto_ptr<Investment> pInv1(Investment::createInvestment());
    auto_ptr<Investment> pInv2(pInv1);
    printf("pInv1 = %p, pInv2 = %p\n", pInv1.get(), pInv2.get());
    auto_ptr<Investment> pInv3 = pInv2;
    printf("pInv2 = %p, pInv3 = %p\n", pInv2.get(), pInv3.get());  //get()函数可以获取到实际指针

    printf("***************************************************\n");
    shared_ptr<Investment> pSInv1(Investment::createInvestment());
    shared_ptr<Investment> pSInv2(pSInv1);
    printf("pSInv1 = %p, pSInv2 = %p\n", pSInv1.get(), pSInv2.get());
    shared_ptr<Investment> pSInv3 = pSInv1;
    printf("pSInv2 = %p, pSInv3 = %p\n", pSInv2.get(), pSInv3.get());

    return 0;
}
//运行结果:
new space
pInv1 = 0x0, pInv2 = 0x7ffb79c02a30
pInv2 = 0x0, pInv3 = 0x7ffb79c02a30
***************************************************
new space
pSInv1 = 0x7ffb79c02a40, pSInv2 = 0x7ffb79c02a40
pSInv2 = 0x7ffb79c02a40, pSInv3 = 0x7ffb79c02a40
delete space   
delete space  

         所以,在使用时,经常使用的是shared_ptr,它比auto_ptr更加强大。但在智能指针中,析构时,它们做的都是delete操作,而非delete[],也就是说,如果这样:

int main()
{
    auto_ptr<string> pInv1(new string[10]);
    shared_ptr<int> pInv2(new int[1024]);

    return 0;
}

        程序可以编译通过,但是运行时会发生析构错误:

//运行结果:
test(2638,0x10ea8f5c0) malloc: *** error for object 0x7fe1c8c029f8: pointer being freed was not allocated
test(2638,0x10ea8f5c0) malloc: *** set a breakpoint in malloc_error_break to debug
Abort trap: 6

        在使用智能指针时,最好不要创建数组类型,有专门处理数组类型的智能指针。
        前面介绍的智能指针管理内存资源,有时具有很大的局限性,假如我们不是只想在程序结束时delete对象,而是想做一些反初始化操作,或者在多线程并发编程中,涉及加锁和释放锁的情形中,程序结束只是要释放锁而非删除锁,这种时候,使用智能指针的另一个功能,就可以达到我们想要的目的。

#include <iostream>
#include <stdio.h>

using namespace std;

class Mutex
{
public:
    static void lock(Mutex *pm)
    {
        cout << "lock" << endl;
    }

    static void unlock(Mutex *pm)
    {
        cout << "unlock" << endl;
    }
};

class Lock
{
public:
    explicit Lock(Mutex* pm): mutexPtr(pm)
    {
        Mutex::lock(mutexPtr);
    }

    ~Lock()
    {
        Mutex::unlock(mutexPtr);
    }

private:
    Mutex *mutexPtr;
};

int main()
{
    Mutex m;
    Lock l1(&m);
    //...其他需要加锁的操作

    return 0;
}
//运行结果:
lock
unlock

        上面的代码虽然没有问题,但如果涉及Lock对象的类似复制一类的操作时,就可能有问题出现,这时,使用智能指针的“删除器”功能,其内部的引用计数法可以自动释放锁,不会造成死锁,只需要构造时,使用智能指针的另一种形式即可:

class Lock1
{
public:
    explicit Lock1(Mutex* pm): mutexPtr(pm, Mutex::unlock)    //shared_ptr的第二个参数,就是用来指定自动“析构”时,不执行默认的析构函数,而是执行我们指定的函数指针。
    {
        Mutex::lock(mutexPtr.get());
    }

private:
    shared_ptr<Mutex> mutexPtr;
};

int main()
{
    Mutex m;
    Lock1 l1(&m);
    //...其他需要加锁的操作

    return 0;
}
//运行结果:
lock
unlock

        上面的函数中,在lock(Mutex *pm)函数中,需要的是一个Mutex型的指针,而我们mutexPtr是一个智能指针,也就是shared_ptr类的对象,如果直接在lock中传mutexPtr是无法编译通过的,所以,shared_ptr和auto_ptr都提供了显式的转换函数get(),可以返回智能指针内部的原始指针。并且,智能指针内部重载了operator->和operator*操作符,它们可以隐式的转换为原始指针,这样我们就可以像使用普通指针一样使用它们:

class Investmemt
{
public:
    bool isTaxFree() const;
    //...

};

Investment* createInvestment();

std::shared_ptr<Investment> pi1(createInvestment());
bool taxable1 = !(pi1->isTaxFree());  //经由operator->访问资源
//...

std::auto_ptr<Investment> pi2(createInvestment());
bool taxable2 = !((*pi2).isTaxFree()); //经由operator*访问资源
//...

        另外,我们在手动使用new和delete时,要保证成对使用,使用new分配的内存最终一定使用delete删除,使用new[]分配的内存最终一定使用delete[]删除,混用的话,虽然不会有报错,但是会造成泄漏。一般来说,我们在程序中不会经常犯new和delete混用的错误,但是大型程序中,很多时候,很多数据类型都是使用typedef进行定义的,如下这种定义就很可能造成new和delete的错误使用:

typedef std::string AddressLines[4];

//如果这样使用new
std::string* pal = new AddressLines;

//析构时,应使用如下形式,因为new AddressLines等同于new string[4]
delete[] pal;

        所以最好不要typedef使用数组形式,如果必须这样做,就必须在代码的注释中写清楚应使用何种析构形式。

        在我们调用含有智能指针的函数时,例如:

void processWidget(std::shared_ptr<Widget> pw, int priority);

        一般调用流程是:

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

        这种调用方式没有问题,但是它也有缺陷,new Widget一定先于shared_ptr的构造函数执行,priority()的执行可能在最先进行也可能在new Widget后进行,如果它最先执行,失败的话,不会有任何影响,但所如果它在new Widget之后进行,则会造成new出来的一块内存还未置入shared_ptr内而造成内存泄漏。所以这样的函数,一般分开去调用更加保险:

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

        就算priority()执行失败,new Widget也会被shared_ptr析构,不会有泄漏风险。在平时写代码中,要多多使用智能指针去健壮我们的代码,避免泄漏及死锁问题的发生。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值