C++升级之整洁之道(三)——高级概念——资源管理

1.资源管理

对软件开发人员来说,管理资源是一项基本业务。大量的各种各样的资源必须合理分配、使用以及使用后归还。主要的资源包括以下几个方面:

内存(栈内存和堆内存);访问硬盘或者其他介质上的文件(读/写)所需的文件句柄;网络连接(例如:连接服务器、数据库);线程、锁、定时器和事务;其他操作系统资源;

处理堆上分配的资源:

void doSomething()
{
    ResourceType* resource = new ResourceType();
    try
    {
        //do something……
    }
    catch(……)
    {
        delete resource ;
        throw;
    }

    delete resource;
}

资源泄露是一个很严重的问题,特别是那种生命周期长的进程,或者是快速分配很多资源而没有立即释放的进程。如果操作系统缺乏资源,这将直接导致临界系统状态。

一个最简单的方法就是在栈上分配内存,而不是堆上。

保证分配的资源总是被释放的方法:

(1)资源申请即初始化

该术语称为:“构造时获得,析构时释放”。

template<typename RESTYPE>
class ScopedResource final
{
    public:
        ScopedResource()
        {
            managedResource = new RESTYPE();
        }

        ~ScopedResource()
        {
            delete managedResource;
        }

        RESTYPE* operator->() const 
        {
            return managedResource;
        }
    private:
        RESTYPE* managedResource;
};

#include "ScopedResource.h"
#include "ResourceType.h"

void dosomething()
{
    ScopedResource<ResourceType> resource;
    
    try
    {
        resource->foo;
    }

    catch(……)
    {
        throw;
    }
}

被包装的类实例化资源就会通过调用它的析构函数自动释放,但是一般情况下不需要重新造轮子就使用智能指针。

(2)使用智能指针

具有独占所有权的std::unique_ptr<T>,定义在<memory>头文件中,管理了一个指向T类型对象的指针。这个智能指针提供的是独占的所有权,一个对象一次只能由std::unique_ptr<T>的一个实例拥有。

#include <memory>

class ResourceType
{
    //……
};

std::unique_ptr<ResourceType> resource1
{
    std::make_unique<ResourceType>();
}

auto resource2
{
    std::make_unique<ResourceType>();
}

基于上面的构造方式,resource的使用非常类似于指向ResourceType的裸指针。可以使用*和->操作符来间接引用指针:resource->foo();当然,如果超出了resource的作用域,resource即能够安全地释放其所持有的ResourceType类型的实例。但最好的是,resource可以很容易地放入容器中。

PS:不要在代码中使用std::auto_ptr<T>!在最新的C++17标准中,这个智能指针模板类已经从C++语言中删除了。

具有共享所有权的std::shared_ptr<T>,也可以与std::share_ptr<T>的其他实例共享这个所有权。其提供了简单且有限的垃圾回收功能。这个智能指针的内部实现有一个引用计数器,用于监控当前有多少个std::shared_ptr<T>的实例。如果智能指针的最后一个实例被销毁,智能指针就会释放它持有的资源。与前面讨论过的std::shared_ptr<T>相反,std::shared_ptr<T>是可以拷贝的。同时,你也可以强制使用std::move<T>来移动它指向的资源:

无所有权但是能够安全访问的std::weak_ptr<T>:有时候,一个没有持有资源的指针指向一个或多个std::shared_ptr<T>实例持有的资源,这是非常必要的。可以通过调用共享指针的get()成员函数。

如果std::shared_ptr<T>的最后一个实例在程序的某个地方被释放,而这个原始指针仍然在某个地方被使用,这个原始指针将变成野指针。如果你需要一个没有所有权的指针,你应该使用std::weak_ptr<T>,它对资源的生命周期没有影响。std::weak_ptr<T>仅仅“观察”它指向的资源,并检查该资源是否有效。在使用缓存对象的地方,std::weak_ptr<T>的实例用于指向这些对象,但是不拥有对象的所有权。如果std::weak_ptr<T>实例的expired()成员函数返回true,那么垃圾回收进程从缓存中清除了该指针指向的对象。另外一种情况,可以使用std::weak_ptr<T>::lock()函数获取std::shared_ptr<T>的实例,这样,即使垃圾回收进程处于活动动态,也可以安全地使用这个对象。垃圾回收进程需评估std::shared_ptr<T>的引用计数,根据引用计数为0时,垃圾回收进程从缓存中删除这个对象,这样不会影响它的使用者。对于处理循环依赖问题,如果你有一个类A需要拥有一个指向另一个类B的指针,同时,类B需要拥有一个指向类A的指针,这样就会产生循环依赖问题。如果用std::shared_ptr<T>指向相应的其他类,可能会导致内存泄漏。原因是在各自的共享指针实例中,引用计数永远不会为0,因此,对象永远不会被删除。如果将A和B类中的std::share_ptr<T>的成员变量类型替换为std::weak_ptr<T>类型,就能解决内存泄漏的问题。基本上说,循环依赖是应用程序代码中糟糕的设计。

(3)避免显式的new和delete

尽可能使用栈内存,其内存分配简单而且安全,永远不会造成内存泄漏。资源一旦超过它的使用范围就会被销毁,你甚至可以通过函数来返回值的类型,这样,就会将直接获取取值的方式转为函数调用的方式。

用make functions在堆上分配资源。用std::make_unique<T>或std::make_shared<T>实例化资源,然后将它包装成一个资源管理对象去管理资源及智能指针。

尽量使用容器(标准库、Boost或者其他)。容器会对其元素进行存储空间的管理。相反,在你自己开发数据结构或序列式容器的时候,你必须自己实现所有的内存管理细节,这将是一个复杂的,且容易出错的任务。

如果有特殊的内存管理,利用特有的第三方库封装资源。

(4)管理特有资源

有时候其他资源不是用new和delete运算符在堆上申请和释放的。其中的一些例子,比如在文件系统中打开文件、动态链接模块(如Windows操作系统上的动态链接库(DLL)),以及图形界面的特殊平台对象(如窗口对象、按钮对象、文本框输入对象等)。

通常,这些资源是通过所谓的句柄(handle)来管理的。句柄是操作系统的资源的一个抽象以及唯一的引用。在Windows平台上,用HANDLE这种数据类型定义这些句柄。事实上,该数据类型定义在头文件WinNT.h中,这个C风格的头文件定义了很多WIN32API的宏和类型:Typedef void *HANDLE,当你用完句柄后,你必须用CloseHandle()函数释放该句柄。如果你想用共享·的句柄,你必须注意在对象构造时应当传一个自定义的句柄删除器(Win32HandleCloser)作为构造函数的参数。

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值