智能指针+守卫锁--借于RAII思想

本文深入探讨了RAII(资源获得即初始化)概念及其在智能指针设计中的应用,详细解析了C++中不同类型的智能指针,包括auto_ptr、unique_ptr、shared_ptr和weak_ptr的工作原理及其实现细节,特别关注了它们如何解决资源管理和线程安全问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RAII(Resource Acquisition Is Initialization)(资源获得即初始化)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量)等的简单技术–本质托管资源-管理权转移,托管权利交给你,自己置空不管
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源
托管给对象的好处:
(1)不需要显式地释放资源。
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效
采用RAII思想进行了智能指针的设计但是不是意味着RAII是智能指针,智能指针是RAII思想的一种实现
构造函数的时候把指针保存起来,然后析构函数自动释放
tempalte
smartptr{
public:
smartptr(T* ptr):_ptr(null){
}
~smarrtptr(){
if(_ptr){
delete _ptr;
}
}
private:
T* _ptr;
};
但是Smartptr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,所以需要重载* 和->

template<class T>
class smartptr{
shareptr(T*ptr):_ptr(nullptr){}
//解引用的返回值有可能是值也有可能是指针
T& operator*(){//此处可以不用&但是最好用&
return *_ptr;//*(this->ptr)
}
T* operator->(){
return _ptr;//(this->ptr)--所以返回值为指针 ---之后就成了->
}
~smartptr(){
if(_ptr){
delete _ptr;
}
}
private:
T* _ptr;
}

但是有问题–是浅拷贝–非深拷贝
int main(){
smartptrsp1(new int );
smartptrsp2(sp1 );
}
sp2为sp1的拷贝构造—这种时候就出错了指针指向的是同一块内存,而smartptr默认的是浅拷贝同一块内存释放两次程序奔
智能指针模拟的是指针,像指针,深拷贝不合适,本质让sp1和sp2共同管理那块内存
C++98版本的库中就提供了auto_ptr的智能指针
管理权转移交给this指针
template
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
: _ptr(ptr)
{}
管理权转移,交给this指针,ap置空,就可以完成拷贝构造了
auto_ptr(const auto_ptr&ap){
一个人管理它
:ptr(ap._ptr){
ap._ptr = NULL;
//但是有问题,给它传参后自己置空了自己的值没有了,所以严禁使用auto_ptr,会导致悬空的问题,有缺陷
}
//ap1=ap3
autoptr& operator=(AutoPtr& ap)
{
// 检测是否为自己给自己赋值
if(this != &ap)
{
// 释放当前对象中资源
if(_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return this;
}
~AutoPtr()
{
if(_ptr)
delete _ptr;
}
T& operator
() {return _ptr;}
T
operator->() { return _ptr;}
prvite:
T* _ptr;
};
void test_auto_ptr(){
auto_ptrap1(new int);
auto_ptrap2(ap1);
}
//不能使用autoptr有严重的缺陷:当对象拷贝或者赋值后,前面的对象就悬空了
c++11引进uniqueptr—直接防拷贝,就是直接将拷贝和赋值给禁止了
tempalte
class uniqueptr{
uiqueptr(T* ptr(null)):_ptr(null){}
T& operator*(){
return _ptr;//(this->ptr)
}
T* operator(){
return _ptr;//(this->ptr)
}
~uiqueptr(){
if(_ptr){
delete _ptr;
}
}
private:
uniqueptr(uniqueptrconst &)=delete;
uniqueptr&operaotr=(uniqueptrconst &)=delete;
private:
T* _ptr;
}
然后c++11引进了新的智能指针shareptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。

  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
    #include
    #include
    template
    class SharedPtr
    {
    public:
    SharedPtr(T* ptr = nullptr)
    : _ptr(ptr)
    , _pRefCount(new int(1))
    , _pMutex(new mutex)
    {}
    ~SharedPtr() {Release();}
    SharedPtr(const SharedPtr& sp)
    : _ptr(sp._ptr)
    , _pRefCount(sp._pRefCount)
    , _pMutex(sp._pMutex)
    {
    AddRefCount();
    }
    // sp1 = sp2
    SharedPtr& operator=(const SharedPtr& sp)
    {
    //if (this != &sp)
    if (_ptr != sp._ptr)
    {
    // 释放管理的旧资源
    Release();
    // 共享管理新对象的资源,并增加引用计数
    _ptr = sp._ptr;
    _pRefCount = sp._pRefCount;
    _pMutex = sp._pMutex;
    AddRefCount();
    }
    return this;
    }
    T& operator
    () {return _ptr;}
    T
    operator->() {return _ptr;}
    int UseCount() {return _pRefCount;}
    T
    Get() { return _ptr; }
    void AddRefCount()
    {
    // 加锁或者使用加1的原子操作
    _pMutex->lock();
    ++(_pRefCount);
    _pMutex->unlock();
    }
    private:
    void Release()
    {
    bool deleteflag = false;
    _pMutex.lock();
    if (–(
    _pRefCount) == 0)
    {
    delete _ptr;
    delete _pRefCount;
    deleteflag = true;
    }
    _pMutex.unlock();
    if(deleteflag == true)
    delete _pMutex;
    }
    private:
    int* _pRefCount; // 引用计数
    T* _ptr; // 指向管理资源的指针
    mutex* _pMutex; // 互斥锁
    };
    但是shareptr还存在一个问题就是线程安全问题
    有两点
    . 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。

  5. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题
    shareptr循环引用问题:
    shared_ptr node1(new ListNode);
    shared_ptr node2(new ListNode);
    cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;
    node1->_next = node2;
    node2->_prev = node1;
    cout << node1.use_count() << endl;
    cout << node2.use_count() << endl;
    return 0; }
    node2 引用计数为1,node1 引用计数为1
    _next也是智能指针,node2变成2
    _prevet也是智能指针,node1变成了 2,
    node1和node2都调用析构计数减减变成1,但是谁都不会释放
    _prev依赖node1这个空间,node1又依赖_next(是该空间的成员)而它又依赖node2—>_preve,死循环就无法释放了
    node2空间释放取决于_next,而node1空间释放取决于_prev
    在这里插入图片描述
    解决方案:
    两个空间智能指针相互作用—>弱指针,不增加引用计数,我可以管理指向你但是不管你的析构不增加引用计数,接收share_Ptr赋值给它
    struct listnode{
    std::weak_ptr _next;
    std::weak_ptr _prev;
    ~listnode(){
    cout<<"~listnode"<<endl;
    }
    };
    int main()
    weak_ptr不增加引用计数
    {
    std::weak_ptr node1(new listnode);
    std::weak_ptr node2(new listnode);
    cout<<node1.use_count()<<endl;
    cout<<node2.use_count()<<endl;
    node->_next=node2;
    node->prev=node1;
    cout<<node1.use_count()<<endl;
    cout<<node2.use_count()<<endl;
    }
    引用计数不变始终是1,调用weak _ptr就可以保证用share_ptr但是引用计数不会增加
    两个空间两个空间智能指针相互作用(循环引用)—>弱指针
    智能指针管new出来的
    如果不是new出来的对象怎么解决?
    int main(){
    share_ptrsp1(new BB);–>对
    share_ptrsp2((BB*)malloc(sizetof(BB)));–>不行
    没有初始化
    share_ptrsp3((string*)malloc(sizeof(string)));—>出错
    没有初始化,string对象有指针指向资源,new出来,malloc全是随机值,也有指针但是是随机值释放不行
    二者malloc区别:
    share_ptrsp4(new BB[10]);–>不行
    –>仿函数解决—实现删除器—shareptr提供

    template
    struct FreeFunc {
    void operator()(T* ptr)
    {
    cout << “free:” << ptr << endl;
    free(ptr);
    }
    };
    template
    struct DeleteArrayFunc {
    void operator()(T* ptr)
    {
    cout << “delete[]” << ptr << endl;
    delete[] ptr;
    }
    };
    int main()
    {
    FreeFunc freeFunc;
    shared_ptr sp1((int*)malloc(4), freeFunc);
    DeleteArrayFunc deleteArrayFunc;
    shared_ptr sp2((int*)malloc(4), deleteArrayFunc);
    return 0; }
    RAII思想实现守卫锁
    tempate
    class lock_gard{
    lock_guar(Mutex mutex):_mtx(mutex)
    {
    _mtx.lock();
    }
    ~lock_guar(){
    _mtx.unlock;
    }
    lock_gard(const lock_gard<Mutex&>)=delete;
    private:
    Mutex& _mtx;//
    }必须使用引用,不然锁的是同一个锁,锁不住,锁的不是一个互斥量对象

<think>好的,用户现在问的是关于C++RAII机制。之前他让我分析了一段代码,里面涉及到了文件操作和管理,而我在之前的回答中提到了建议使用RAII来改进代码的健壮性。现在他可能想更深入地了解RAII是什么,以及为什么在之前的例子中使用RAII会更好。 首先,我需要确认用户对RAII的了解程度。他可能有一定的C++基础,但可能对资源管理的最佳实践不太熟悉。在之前的代码分析中,用户使用了裸指针和手动加,这可能导致资源泄漏,尤其是在发生错误或异常时。所以用户可能想知道如何避免这类问题,而RAII正是解决这类问题的关键机制。 接下来,我需要解释RAII的基本概念,即资源获取即初始化。要强调RAII如何将资源的管理与对象的生命周期绑定,确保资源在对象析构时自动释放。这可以防止内存泄漏、文件未关闭、未释放等问题。 然后,需要举几个例子来说明RAII的应用,比如智能指针(如unique_ptr和shared_ptr)、文件流对象(如fstream)以及守卫(如lock_guard)。这些例子能帮助用户理解RAII在实际中的使用方式,特别是用户之前的代码中使用了手动管理和文件操作,用lock_guard和fstream替代的话会更安全。 还需要对比用户之前的代码,说明如果使用RAII的话,如何避免手动调用lock/unlock或fopen/fclose,从而减少错误。例如,使用lock_guard可以在构造时加,析构时自动解,即使在发生异常时也能保证解,避免死。 同时,要指出RAII的优势,比如异常安全、代码简洁、可维护性高。用户可能关心如何写出更健壮的代码,尤其是在多线程或复杂资源管理的情况下,RAII能提供更可靠的保障。 另外,可能需要提醒用户RAII是C++的核心思想之一,现代C++标准库中的许多组件都是基于RAII设计的,因此学习和应用RAII对于编写高质量的C++代码非常重要。 最后,总结一下RAII的重要性,并建议用户在实际项目中多使用智能指针、容器类、守卫RAII封装类,以提高代码的安全性和可维护性。如果有必要,还可以推荐一些进一步学习的资源,比如C++标准库文档或相关书籍。</think>### **C++RAII 机制:核心思想与实战意义** **RAII(Resource Acquisition Is Initialization,资源获取即初始化)** 是 C++ 的核心设计理念之一,其核心思想是:**通过对象的生命周期管理资源,资源在对象构造时获取,在对象析构时自动释放**。这一机制彻底改变了 C++ 的资源管理方式,使得内存、文件、等资源的管理变得安全、简洁且可靠。 --- ### **1. RAII 的核心原理** - **资源绑定对象**:将资源(如内存、文件句柄、)的获取与对象的构造函数绑定,释放与析构函数绑定。 - **自动释放**:无论程序正常执行还是发生异常,对象析构时资源必然被释放,避免资源泄漏。 - **所有权明确**:对象对资源拥有明确的所有权,避免悬空指针或重复释放等问题。 --- ### **2. RAII 的经典应用场景** #### **(1) 内存管理:智能指针** ```cpp // 传统裸指针:需手动释放,易泄漏 int* raw_ptr = new int(42); delete raw_ptr; // 忘记则内存泄漏 // RAII 智能指针:自动释放 #include <memory> std::unique_ptr<int&gt; smart_ptr = std::make_unique<int&gt;(42); // 离开作用域时自动调用 delete ``` - **关键类型**:`std::unique_ptr`(独占所有权)、`std::shared_ptr`(共享所有权)、`std::weak_ptr`(避免循环引用)。 #### **(2) 文件操作:文件流对象** ```cpp // 传统方式:需手动关闭文件 FILE* file = fopen("data.txt&quot;, "r"); if (file) { // 操作文件 fclose(file); // 忘记则文件句柄泄漏 } // RAII 方式:std::fstream #include <fstream> std::ifstream file("data.txt&quot;); if (file.is_open()) { // 操作文件 } // 离开作用域时自动关闭文件 ``` #### **(3) 管理:守卫(Lock Guard)** ```cpp // 传统方式:需手动解 std::mutex mtx; mtx.lock(); // 临界区操作 mtx.unlock(); // 忘记则死 // RAII 方式:std::lock_guard #include <mutex> std::mutex mtx; { std::lock_guard<std::mutex> lock(mtx); // 构造时加 // 临界区操作 } // 离开作用域时自动解 ``` --- ### **3. RAII 的核心优势** | **场景** | **传统方式风险** | **RAII 解决方案** | |---|--------------------------------| | 内存分配 | 忘记 `delete` 导致泄漏 | `std::unique_ptr` 自动释放 | | 文件操作 | 忘记 `fclose` 句柄泄漏 | `std::fstream` 自动关闭 | | 多线程 | 忘记 `unlock` 导致死 | `std::lock_guard` 自动解 | | 异常安全 | 异常跳过资源释放代码 | 析构函数必然执行,保证释放 | --- ### **4. RAII 的设计哲学** - **确定性析构**:C++ 对象的析构函数调用时机是确定的(如离开作用域、`delete` 触发等),确保资源释放的可靠性。 - **异常安全**:即使代码中抛出异常,RAII 对象析构仍会执行,避免资源泄漏。 - **零额外开销**:RAII 通过编译器生成的代码管理资源,无运行时性能损耗。 --- ### **5. 实战意义:对比原始代码的改进** 假设原始代码片段如下: ```cpp // 原始代码(存在资源泄漏风险) void unsafe_write() { FILE* file = fopen("data.tmp", "wb"); if (file) { mtx.lock(); fwrite(data, sizeof(data), 1, file); mtx.unlock(); fclose(file); } } ``` - **问题**:若 `fwrite` 抛出异常,`mtx.unlock()` 和 `fclose` 可能被跳过,导致死和文件句柄泄漏。 **RAII 改进后**: ```cpp void safe_write() { std::ofstream file("data.tmp", std::ios::binary); if (file.is_open()) { std::lock_guard<std::mutex> lock(mtx); // 自动加/解 file.write(data, sizeof(data)); } // 自动关闭文件 + 自动解 } ``` - **优势**:无论是否发生异常,文件必然关闭,必然释放。 --- ### **6. 总结:为什么 RAII 是 C++ 的灵魂?** RAII 不仅解决了资源管理问题,更体现了 C++ **“资源即对象”** 的设计哲学。它是现代 C++ 的基石,支撑着智能指针、容器、并发库等核心组件。**掌握 RAII,才能写出真正高效、安全、简洁的 C++ 代码**。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值