基于智能指针和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);

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

查看所有标签

猜你喜欢:

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

JAVA多线程设计模式

JAVA多线程设计模式

结城 浩、博硕文化 / 博硕文化 / 中国铁道出版社 / 2005-4-1 / 49.00元

《JAVA多线程设计模式》中包含JAVA线程的介绍导读,12个重要的线程设计模式和全书总结以及丰富的附录内容。每一章相关线程设计模式的介绍,都举一反三使读者学习更有效率。最后附上练习问题,让读者可以温故而知新,能快速地吸收书中的精华,书中最后附上练习问题解答,方便读者学习验证。一起来看看 《JAVA多线程设计模式》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试