C++智能指针

本文介绍了C++中的智能指针,包括std::unique_ptr、std::shared_ptr和std::weak_ptr,讨论了RAII原则的应用、auto_ptr的问题、线程安全问题以及循环引用的解决方案。作者还提到了如何使用定制删除器来处理非new分配的资源。

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

C++智能指针

智能指针是一种用于管理动态分配内存的对象,可以在内存不再需要时自动释放。智能指针通过重载了指针操作符的类来实现,以模拟指针的行为,但具有自动资源管理的功能。

RAII思想

RAII 是资源获取即初始化(Resource Acquisition Is Initialization)的缩写,是一种 C++ 编程范式,它通过在对象的构造函数中获取资源,利用对象的生命周期来管理资源的释放,从而确保资源在不再需要时被正确释放。智能指针是RAII思想的一种产物,在多线程中,守卫锁Guard Lock也是一种常见的RAII风格的加锁方式。

RAII风格的Lock Guard(Linux下原生线程库)

#pragma once
#include <iostream>
#include <pthread.h>
//RAII枷鎖
class Mutex
{
public:
 Mutex(pthread_mutex_t* mutex)
 :_mutex(mutex)
 {
     pthread_mutex_init(_mutex,nullptr);
 }
 void lock()
 {
     pthread_mutex_lock(_mutex);
 }
 void unlock()
 {
     pthread_mutex_unlock(_mutex);
 }
 ~Mutex()
 {
     pthread_mutex_destroy(_mutex);
 }
private:
 pthread_mutex_t* _mutex;
};

class LockGuard
{
public:
 LockGuard(pthread_mutex_t* pmtx)
 :_mtx(pmtx)
 {
     _mtx.lock();
 }
 ~LockGuard()
 {
     _mtx.unlock();
}
private:
 Mutex _mtx;
};

智能指针

C++标准库提供了两种主要的智能指针:std::unique_ptrstd::shared_ptr。此外,C++17还引入了 std::weak_ptr

std::auto_ptr

C++在C++98中就引入了auto_ptr,但是但在 C++11 标准中已经被废弃,并在 C++17 中被完全移除。这是因为 auto_ptr 存在一些严重的缺陷。

问题如下:

int main()
{
    std::auto_ptr<int> p1(new int);
    std::auto_ptr<int> p2(p1);
    *p1 = 10;
    *p2 = 10;
    std::cout << *p1 << std::endl;
    std::cout << *p2 << std::endl;
    return 0;
}

上面的代码以指针的角度来看,就是让两个指针维护同一块地址空间,但是上面程序运行会奔溃。这是标准库中的auto_ptr

简单实现一份auto_ptr然后了解一下auto_ptr的问题。

#pragma once
#include <iostream>
namespace ding
{
    template<class T>
    class auto_ptr
    {
    public:
        auto_ptr(T* ptr)
            :_ptr(ptr)
        {}
        auto_ptr(auto_ptr<T> & sp)
            :_ptr(sp._ptr)
        {
            sp._ptr = nullptr;
        }
        //赋值运算符(注意先释放原空间在赋值 以及自己给自己赋值的情况)
        auto_ptr<T>& operator=(const auto_ptr<T>& sp)
        {
            if (&sp != this)
            {
                if (_ptr != nullptr)
                {
                    delete _ptr;
                }
                _ptr = sp._ptr;
                sp._ptr = nullptr;
            }
            return *this;
        }
        //模拟指针行为
        T& operator*() const
        {
            return *_ptr;
        }
        T* operator->() const
        {
            return _ptr;
        }
        ~auto_ptr()
        {
            if (_ptr != nullptr)
            {
                delete _ptr;
            }
        }
    private:
        T* _ptr;
    };
}
​

使用auto_ptr

#include "auto_ptr.h"
int main()
{
    ding::auto_ptr<int> p1(new int);
    ding::auto_ptr<int> p2(p1);
    *p1 = 10;
    *p2 = 10;
    std::cout << *p1 << std::endl;
    std::cout << *p2 << std::endl;
    return 0;
}

当执行完ding::auto_ptr<int> p2(p1);后,p1对象的指针已经被置空了,在对其解引用,就会出现对空指针解用的问题。空指针是不能解引用的。这种情况编译器应该要出警告的,但是我的编译器还是能运行的,只是退出码不正常。这应该是vs2022的bug(我用的是2022测试版的)。

只能通过监视窗口来看!

image.png

C++98提供的auto_ptr最大的问题就是这个了,称之为管理权转移,将p1的管理权转移给p2然后自己悬空。导致了auto_ptr直接被禁用,甚至在17中被移除了。

std::unique_ptr

经过十几年后,在C++11中,出现了比auto_ptr更靠谱的unique_ptrunique_ptr主要解决auto_ptr带来的问题,在unique_ptr中直接禁用了拷贝和赋值。

std::unique_ptr的使用

int main()
{
    std::unique_ptr<int> p1(new int);
    std::unique_ptr<int> p2(p1);//error
    p2 = p1;//error
}

unique_ptr不能再使用拷贝和赋值了,所以他的使用场景就被限制了,对于有些地址空间需要更多的指针来维护是不能实现的。

std::unique_ptr简易模拟实现

unique_ptr就很简单了,对比auto_ptr直接把拷贝构造和赋值用C++11的语法用delete禁用就行了。

namespace ding
{
    template<class T>
    class unique_ptr
    {
    public:
        unique_ptr(T* ptr)
            :_ptr(ptr)
        {}
        unique_ptr(unique_ptr<T>& sp) = delete;
        //赋值运算符
        unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
        
        //模拟指针行为
        T& operator*() const
        {
            return *_ptr;
        }
        T* operator->() const
        {
            return _ptr;
        }
        ~unique_ptr()
        {
            if (_ptr != nullptr)
            {
                delete _ptr;
            }
        }
    private:
        T* _ptr;
    };
}

std::shared_ptr

C++11还提供了更靠谱的智能指针shared_ptr。解决了auto_ptr的悬空问题和unique_ptr的防拷贝问题。

std::shared_ptr 使用引用计数来跟踪有多少个智能指针指向相同的资源,当最后一个 std::shared_ptr 被销毁时,它所管理的资源也会被释放。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

image.png

shared_ptr 简易模拟实现

引用计数是shared_ptr对象的公共资源,也就是说在实现shared_prt时,不能简单的将引用计数_ref_count设计为一个普通的成员变量,设计成为普通的成员变量,就意味着每个shared_ptr对象都有一个引用计数。普通的成员变量导致无法正确记录内存块的使用情况。

图解:

image.png

使用static也不能解决,因为static是所有类对象共享的。这就会导致只要使用shared_ptr不论管理的是哪一块地址空间,用的都是同一个引用计数。
图解:

image.png

将引用计数定义成为一个指针,当一块地址空间第一次被shared_ptr对象维护时,在堆区开辟一块空间用于存储其对应得引用计数,如果其他shared_ptr对象也要维护这块地址空间,除了将地址给他还要把引用计数的地址也给他。
图解:

image.png

namespace ding
{
    template<class T>
    class shared_ptr
    {
    public:
            shared_ptr(T* ptr = nullptr)
                    :_ptr(ptr)
                    ,_ref_count(new size_t(1))
            {}
            shared_ptr(shared_ptr<T>& sp)
                    :_ptr(sp._ptr)
                    ,_ref_count(sp._ref_count)
            {
                    ++(*_ref_count);
            }
            shared_ptr<T>& operator=(shared_ptr<T>& sp)
            {
                    if (this != &sp)
                    {
                            if (--(*_ref_count) == 0)
                            {
                                    delete _ref_count;
                                    delete _ptr;
                            }
                            _ptr = sp._ptr;
                            _ref_count = sp._ref_count;
                            ++(*_ref_count);
                    }
                    return *this;
            }
            T& operator*() const 
            {
                    return *_ptr;
            }
            T* operator->()const
            {
                    return _ptr;
            }
            ~shared_ptr()
            {
                    if (--(*_ref_count) == 0 && _ptr != nullptr)
                    {
                            delete _ptr;
                            delete _ref_count;
                    }
            }
    private:
            T* _ptr;
            size_t* _ref_count;//引用计数
    };
}

通过监视窗口可以看出当前代码得sp1,sp2,sp3都指向得是同一块地址空间。引用计数都是3。没有问题。
image.png
执行完sp3 = sp4 后,sp1和sp2得引用计数应该变为2,sp3和sp4应该指向同一块地址空间,引用计数也是同一个。
监视窗口观看也没有问题。
image.png

如果是普通得成员变量得话,上面同样得代码,监视窗口结果如下:

引用计数得结果完全不符合要求。
image.png
image.png

shared_ptr线程安全问题

std::sharer_ptr本身是线程安全得,当多个线程同时访问同一个 std::shared_ptr 对象时,std::shared_ptr 本身能够确保引用计数的操作是原子的,从而保证了线程安全性。
比如下面得场景:

创建两个线程,让他们疯狂的拷贝当前智能指针对象,引用计数就会一直增加。
线程执行完后,引用计数应该还是1。因为copy对象是一个局部对象,出了作用域就释放了。

void fun(std::shared_ptr<int
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C++下等马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值