C++新特性-智能指针

介绍

智能指针基于RAII的思想设计。

RAII:利用对象生命周期来控制程序资源。在对象构造时获取资源,在对象析构时释放资源,则不再需要显式释放资源,同时在对象的生命周期内,对该资源的访问始终有效。即将管理资源的责任托管给了对象。

智能指针本质上就是使用对象对裸指针进行封装,在析构函数中delete该指针指向的内存,同时重载该对象的*->运算符模拟指针的操作。则在对象生命周期结束时,会自动调用其析构,即自动释放掉其管理的资源。

简单的智能指针实现如下(注:文章中代码不够严谨,仅用于表达设计原理):

cpp 复制代码
template <class T>
class smart_ptr
{
public:
    smart_ptr(T *ptr) : _ptr(ptr) {}
    ~smart_ptr(){ delete _ptr; }

    T &operator*(){ return *_ptr; }
    T *operator->(){ return _ptr; }

private:
    T *_ptr;
};

上述简单的智能指针的问题在于:多个智能指针指向同一份资源可能出现资源被多次释放问题。如:

cpp 复制代码
smart_ptr<int> p1(new int);
smart_ptr<int> p2=p1; // p1和p2指向同一份资源

针对不同的解决方法则有了多种类型的智能指针:

  1. 管理权转移-C++98的auto_ptr
  2. 防拷贝-C++11的unique_ptr
  3. 引用计数的共享拷贝-C++11的shared_ptr

auto_ptr

auto_ptr在C++98中被提出,其只是简单的将源指针置为nullptr来达到转移的效果,即:

cpp 复制代码
auto_ptr(auto_ptr<T>& p)
{
    _ptr=p._ptr;
    p._ptr=nullptr;
}

但是该设计并不好,在C++11中被弃用。

unique_ptr

其简单粗暴,直接就不提供拷贝构造和赋值拷贝,达到防拷贝的效果。即

cpp 复制代码
unique_ptr(unique<T>& p)=delete;
unique_ptr<T>& operator=(unique_ptr<T>& p)=delete;

shared_ptr

指向同一资源的多个shared_ptr共同管理一份引用计数,每个shared_ptr对象则持有该引用计数的指针。

cpp 复制代码
T *_ptr;
int *_count; // 引用计数

shared_ptr(T *ptr) : _ptr(ptr), _count(new int(1)) {}
~shared_ptr(){ release(); }
shared_ptr(const shared_ptr<T> &p) : _ptr(p._ptr), _count(p._count){ ++(*_count); }

shared_ptr<T> &operator=(const shared_ptr<T> &p)
{
    if (this != &p)
    {
        release();
        _ptr = p._ptr;
        _count = p._count;
        ++(*_count);
    }
    return *this;
}

void release()
{
    if (--(*_count) == 0)
    {
        delete _ptr;
        _ptr=nullptr;
        
        delete _count; 
        _count=nullptr;
    }
}

shared_ptr的线程安全问题

上述的shared_ptr当在不同线程中同时进行拷贝构造、赋值拷贝、析构,极有可能会导致其*_count出错。

故:引入互斥锁,保证并发安全。

cpp 复制代码
T *_ptr;
int *_count;
std::mutex *_mtx; // 互斥锁

void add_ref_count()
{
    _mtx->lock();
    ++(*_count);
    _mtx->unlock();
}

void release()
{
    _mtx->lock();
    bool need_delete = false;
    if (--(*_count) == 0)
    {
        delete _ptr;
        _ptr = nullptr;
        delete _count;
        _count = nullptr;
        need_delete = true;
    }
    _mtx->unlock();
    if (need_delete)
    {
        delete _mtx;
        _mtx = nullptr;
    }
}

weak_ptr

shared_ptr的循环引用问题

考虑以下场景:

cpp 复制代码
struct ListNode
{
    int val;
    shared_ptr<ListNode> prev;
    shared_ptr<ListNode> next;
};

void fun()
{
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    node1->next=node2;
    node2->prev=node1;
}

由于ListNode中的前置和后置的shared_ptr,node1和node2的引用计数都为2,当fun函数结束析构node1和node2时,其引用计数变为1,并不会释放指向的对应资源。

为了辅助shared_ptr解决该循环引用场景,引入了weak_ptr

weak_ptr

该指针本质上就是指针的封装,但并不控制指向对象的生命周期,并不符合RAII,或者严格上来说其并不是智能指针。

weak_ptr也不会影响shared_ptr的引用计数。

cpp 复制代码
template <class T>
class weak_ptr
{
    public:
    weak_ptr()=default;
    weak_ptr(const shared_ptr<T>& p):_ptr(p.get()){}
    weak_ptr<T> operator=(const shared_ptr<T>& p)
    {
        _ptr=p.get();
        return *this;
    }
    private:
        T* _ptr;
};

则上述的循环引用可使用weak_ptr解决:

cpp 复制代码
struct ListNode
{
    int val;
    weak_ptr<ListNode> prev;
    weak_ptr<ListNode> next;
};

void fun()
{
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    node1->next=node2;
    node2->prev=node1;
}

删除器

上述的智能指针都只是可以自动释放new出来的对象,智能指针封装的并非对象指针,而是如:malloc返回的指针,或是FILE*等资源,则需要手动传入一个删除器仿函数。

通过定制删除器可以定制智能指针中对资源的释放。

cpp 复制代码
template<class T>
class freer
{
    void operator()(T* p){ free p; }
};

int main()
{
    // 第二个参数传入仿函数
    shared_ptr<int> a((int*)malloc(sizeof(int)*10),freer<int>());
}
相关推荐
EveryPossible2 小时前
服务器测试指南
服务器
蜕变的小白2 小时前
Linux系统编程-->高效并发服务器:IO多路复用详解
linux·运维·服务器
Johnstons2 小时前
AnaTraf 网络流量分析免费版:给运维多一双“眼睛”
运维·服务器·网络
rannn_1112 小时前
【Redis|实战篇2】黑马点评|商户查询缓存
java·redis·后端·缓存
峥嵘life2 小时前
Android16 EDLA更新25-12补丁导致【CTS】CtsWindowManagerDeviceAnimations存在fail项
android·linux·学习
草莓熊Lotso2 小时前
手搓简易 Linux 进程池:从 0 到 1 实现基于管道的任务分发系统
linux·运维·服务器·数据库·c++·人工智能
猹叉叉(学习版)2 小时前
【ASP.NET CORE】 9. 托管服务
数据库·笔记·后端·c#·asp.net·.netcore
YMWM_3 小时前
linux文件快速传windows
linux·运维·服务器
星竹晨L3 小时前
Linux开发工具入门(一):开发三板斧(包管理器,vim,gcc/g++) 以及入门理解动静态库
linux·运维·服务器