基于智能指针和RAII的对象内存管理设计

栏目: C++ · 发布时间: 5年前

内容简介:在听起来有点绕是不是,让我们来简化一下其主要特点。

从C++ std::shared_ptr 原理来看看栈溢出的危害 ,我提及了 C++ 的智能指针指向被管理对象的 raw ptr 会被栈内存溢出而破坏,而利用智能指针进行对象构造的管理和设计,可以衍生出和 RAII 的结合,今天就来谈谈这项技术。

什么是RAII?

RAIIResource Acquisition Is Initialization ,简而言之就是将对一个资源的申请封装在一个对象的生命周期内的理念。这样做的好处就是,C++的对象势必在创建的时候会经过构造函数,而在销毁的时候会触发析构函数。

听起来有点绕是不是,让我们来简化一下其主要特点。

  • 所有的资源管理内聚在对象内部
  • 利用对象申请/释放的特性对资源同步进行对应的申请/释放
  • 自动管理对象

前两点都比较容易,那么第三点如何达到呢?

合理的利用局部变量。

绝大多数语言,比如 C++ ,都居于块级作用域。当在创建的变量离开其所在的块级时候,就会触发释放。而这就可以达到我们所说的自动管理对象。

这其实就是压栈/出栈的高级语言表现。

而在 C++ 领域,有一个比较经典的利用 RAII 特性的设计就是 ScopeLock

template<class LockType>

class My_scope_lock
{

   public:

   My_scope_lock(LockType& _lock):m_lock(_lock)

   {

         m_lock.occupy();

    }

   ~My_scope_lock()

   {

        m_lock.relase();

   }

   protected:

   LockType    m_lock;
}

在这里, 锁被看成是一种资源,他需要lock/unlock的配对操作,不然就会引发问题。

而上述代码,将锁保留在对象的构造函数和西沟函数中。这样,当我们在某个函数中需要操作临界区域的时候,就可以简洁明了的使用局部变量来操作锁:

void Data::Update()
{
     My_scope_lock l_lock(m_mutex_lock);
    // do some operation
}

基于智能指针的RAII

上文我们用锁的例子来举例说明了 RAII 的设计理念,那什么又是基于智能指针的 RAII 呢?

我们都知道,在编程过程中,我们必须和内存打交道,而内存分为了两种类型:栈上内存和堆上内存。栈上内存不仅和线程相关,同时空间大小也相对堆内存来说非常小。因此,当我们在处理一些大规模数据(以及对象规模不确定)的时候,比如使用几百个对象的数据等等,一般都采用堆上动态分配内存。

但是堆上内存,在诸多的语言中,都需要手动管理,比如 C++ 。而一般处理不当,比如(new []和delete搭配),或者遗忘了释放,那么就会产生内存泄漏等严重问题。

为此,我们参考上节的设计,准备构建一个可以在对象的构造/析构函数中成对正确释放内存的设计思路。

先假设一个需要在堆上频发操作的对象 Data

class Data {
    // 省略
}

如果直接使用,一般情况下是这样的代码:

Data *data = new Data();
delete data;

需要频繁的确认对堆内存的正确使用。现在我们给他加一个包装对象, DataHandle

class DataHandle {
    private:
        Data *m_data;
}

DataHandle::DataHandle():m_data(new Data())
{}

DataHandle::~DataHandle()
{
    delete m_data;
    m_data = NULL;
}

这样,我们后续每次使用,就可以简化成

{
    DataHandle handle;
}

但是,别忘记了, C++ 中海油拷贝构造和重载赋值等操作,一旦我们写出如下代码,就会引发 double free 的问题。

{
    DataHandle handle1(handle);
    handle1 = handle;
}

因此,我们需要对拷贝构造函数和重载赋值进行特别处理。这里有两种处理方式:

  • 对于拷贝/赋值,每次把内部指针 m_data 也拷贝 new 一次。
  • 对于 m_data 进行合理的计数记录。

一般情况下,我们期望 DataHandle 的行为和 Data 是一致的。 因此我们想使用第二种方式。

这个时候, C++shared_ptr 就派上用场了。改写下 DataHanle

class DataHandle{
    public:
        DataHandle();
        DataHandle(const DataHandle &handle);
        DataHandle& operator=(const DataHanlde &handle);
    private:
        std::shared_ptr<Data> m_dataS;
}

对于重载后的拷贝/复制函数,我们只要利用智能指针自身重载过的赋值操作赋,即可解决引用计数问题。

最后要特别注意的是,下述两种情况的代码,是完全不相同的含义。

// 第一种情况
Data *data = new Data();

std::shared_ptr<Data> s1 = std::shared_ptr(data);

std::shared_ptr<Data> s2 = s1;

// 第二种情况

Data *data = new Data();

std::shared_ptr<Data> s1 = std::shared_ptr(data);

std::shared_ptr<Data> s2 = std::shared_ptr(data);

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ajax模式与最佳实践

Ajax模式与最佳实践

Christian Gross / 李锟、张祖良、蔡毅、赵泽欣 / 电子工业出版社 / 2007-3 / 49.80元

Ajax 正在将我们带入到下一代的网络应用中。 本书深入探讨了动态的网络应用,将Ajax和REST集成在一起作为单独的解决方案。一个很大的优势是,与Ajax相似,REST可以和现今存在的技术一起使用。现在上百万的客户端计算机都是基于Ajax的,上百万的服务器是基于REST的。   无论你是否已经开发过Ajax应用程序,这都是一本理想的书。因为这本书描述了各种各样的模式和最好的实践经验。通过此......一起来看看 《Ajax模式与最佳实践》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具