C++ 智能指针

为什么需要智能指针

智能指针主要解决以下问题:
1.内存泄漏:内存手动释放,使用智能指针可以自动释放
malloc free:new delete
2.共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题
C++里面的四个智能指针: auto_ptr,shared_ptr,unique_ptr,weak_ptr其中后三个是C++11支持,并且第一个已经被C++11弃用。
几个指针的特点:
unique_ptr独占对象的所有权,由于没有引用计数,因此性能较好。
shared_ptr共享对象的所有权,有引用计数但性能略差,
weak_ptr配合shared ptr,解决循环引用的问题

一、shared_ptr

总结
一个shared_ptr对象管理一个指针(new T,在堆空间),多个shared_ptr对象可以管理同一个指针,只有某个shared_ptr对象第一次初始化指针时才执行指针的构造函数,管理同一个指针的shared_ptr对象个数称为引用计数,这个引用计数保存在每个管理该指针的shared_ptr对象中,当引用计数为0时,这个指针执行析构函数释放;shared_ptr对象也可以管理空指针,此时引用计数为0。
shared_ptr做为函数参数传递时,函数运行期间引用计数加一,函数运行完后离开作用域,引用计数减一。

线程安全的问题

由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。
由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。
在这里插入图片描述
shared_ptr是线程安全的吗?对此问题,我们需要从三个并发场景进行考虑,拷贝shared_ptr的安全性、对shared_ptr赋值的安全性和读写shared_ptr指向内存区域的安全性。

对于以上问题,首先给出以下结论:

  • 如果多个线程同时拷贝同一个shared_ptr对象,不会有问题,因为shared_ptr的引用计数是线程安全的。
  • 如果多个线程同时修改同一个shared_ptr 对象,不是线程安全的。
  • 如果多个线程同时读写shared_ptr指向的内存对象,不是线程安全的。
    C++: shared_ptr是线程安全的吗
常用函数
初始化的方式 make_shared
    auto sp1 = make_shared<int>(100); // 优先使用make_shared来构造智能指针
    //相当于
    shared_ptr<int> sp2(new int(100));
    
以下为错误案例!!!!
//    std::shared_ptr<int> p = new int(1);  // 不能将一个原始指针直接赋值给一个智能指针
//      int *p = new int(1);
作用域
#include <iostream>
#include <memory>
using namespace std;
void test(shared_ptr<int> sp) {
    // sp在test里面的作用域
    cout << "test sp.use_count()" << sp.use_count() << endl;
}

int main(int argc, char *argv[])
{
    shared_ptr<int> sp5(new int(100));
    test(sp5);
    cout << "sp5.use_count()" << sp5.use_count() << endl;
    return 0;
}
输出:
test sp.use_count()2
sp5.use_count()1
reset()

当函教没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重詈为一个空指针,当为函数传递个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1

    std::shared_ptr<int> p1;
    p1.reset(new int(1));  // 分配资源。
    if (p1) {
        cout << "p1 ptr new int(1) \n";
    }
//    p1.reset();
//    if(!p1) {
//        cout << "p1 ptr NULL \n";
//    }
    std::shared_ptr<int> p2 = p1;

//    // 引用计数此时应该是2
    cout << "p2.use_count() = " << p2.use_count() << endl;  // 2
    p1.reset();   // 释放资源

//    // 引用计数此时应该是1
    cout << "p2.use_count()= " << p2.use_count() << endl;  // 1
    if (!p1) {  // p1是空的 
        cout << "p1 is empty\n";
    }
    if (!p2) { // p2非空
        cout << "p2 is empty\n";
    }
get

通过智能指针的get接口过去原始指针

        int *p = new int(1);
        std::shared_ptr<int> ptr(p1); // 裸指针委托智能指针管理
        ine *p2 = ptr.get();  
使用shared_prt要注意的问题
1. 不要用一个原始指针初始化多个shared_ptr。因为同一个指针会被释放多次
    int *ptr = new int;
    shared_ptr<int> p1(ptr);
    shared_ptr<int> p2(ptr); // 逻辑错误 同一个指针会被释放多次
2. 不要在函数实参中创建shared_ptr

在这里插入图片描述

3. 通过shared_from_this() 返回智能指针this指针

在这里插入图片描述

#include <iostream>
#include <memory>

using namespace std;

class A : public std::enable_shared_from_this<A> {
public:
    shared_ptr<A> GetSelf() {
        return shared_from_this(); //
    }

    A() {
        cout << "Construction A" << endl;
    }

    ~A() {
        cout << "Destruction A" << endl;
    }
};

int main(int argc, char *argv[])
{
    shared_ptr<A> sp1(new A);
    shared_ptr<A> sp2 = sp1->GetSelf();  // ok
    shared_ptr<A> sp3 = sp1;  // ok

    cout << "sp1.use_count() = " << sp1.use_count() << endl;
    cout << "sp2.use_count() = " << sp2.use_count() << endl;
    cout << "sp3.use_count() = " << sp3.use_count() << endl;

    cout << "leave {}" << endl;

    return 0;
}
4. 避免循环引用

以下为错误案例:错误原因为循环引用导致ap和bp的引用计数为2,在离开作用域后,ap和bp引用计数减为1,并不会减为0,因此导致指针都不会被释放导致内存泄漏。

#include <iostream>
#include <memory>

using namespace std;
class A;
class B;

class A {
public:
    std::shared_ptr<B> bptr; //等待A类被析构后才会-1。但是这个A类需要等待计数为0才会析构,但永远达不到要求。

    ~A() {
        cout << "A is deleted" << endl;
    }
};

class B {
public:
    std::shared_ptr<A> aptr;

    ~B() {
        cout << "B is deleted" << endl;
    }
};

int main() {
    {
        std::shared_ptr<A> ap(new A);
        std::shared_ptr<B> bp(new B);
        ap->bptr = bp;
        bp->aptr = ap;
    }
    cout << "main leave" << endl;  // 循环引用导致ap bp退出了作用域都没有析构
    return 0;
}

二、unique_ptr 独占的智能指针

它持有对对象的独有权——两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作。
参考:std::unique_ptr
在这里插入图片描述
程序例程
1.初始化 2.指定删除器 3.调用函数接口 4.观察析构情况

//1-1-shared_from_this2
#include <iostream>
#include <memory>

class CTest
{
public:
    // 构造函数
    CTest(int num)
        : m_nNum(num)
    {
        std::cout << "CTest::CTest() " << m_nNum << std::endl;
    }

    // 析构函数
    ~CTest()
    {
        std::cout << "CTest::~CTest() " << m_nNum << std::endl;
    }

    void PrintNum()
    {
        std::cout << "PrintNum " << m_nNum << std::endl;
    }

    int m_nNum{ 0 };
};

// 自定义删除器
static void CustomDelete(CTest* p)
{
    std::cout << "CTest::CustomDelete()" << std::endl;
    delete p;
}

int main(int argc, char *argv[]) {
    //std::unique_ptr<CTest> ptr(new CTest(0));
    std::unique_ptr<CTest, void(*)(CTest*)> ptr(new CTest(7), CustomDelete);

    // -> 运算符重载
    ptr->PrintNum();
    // * 运算符重载
    (*ptr).PrintNum();
    // bool 运算符重载
    if (ptr){
        std::cout << "ptr is not nullptr" << std::endl;
    }
    std::unique_ptr<CTest[]> ptr1(new CTest[2]{ 1,2 });
    // [] 运算符重载
    ptr1[1].PrintNum();
    return 0;
}

输出结果:
CTest::CTest() 7
PrintNum 7
PrintNum 7
ptr is not nullptr
CTest::CTest() 1
CTest::CTest() 2
PrintNum 2
CTest::~CTest() 2
CTest::~CTest() 1
CTest::CustomDelete()
CTest::~CTest() 7

三、weak_ptr

  • weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。
  • weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。
  • weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。
  • 由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。
主要函数

expired:检查被引用的对象是否被删除了
lock:获取监视的shared_ptr;

参考:C++智能指针weak_ptr详解

使用例程
1.观察weak_ptr指向对象的作用域,以及expired的使用方法
#include <iostream>
#include <memory>

using namespace std;
std::weak_ptr<int> gw;
void test1() {
    cout << "---- test1 ------------------" << endl;
    weak_ptr<int> wp;
    {
        shared_ptr<int> sp(new int(1));  //sp.use_count()==1
        wp = sp;                            //wp不会改变引用计数,所以sp.use_count()==1
        shared_ptr<int> sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
    }
    shared_ptr<int> sp_null = wp.lock(); //sp_null .use_count()==0;
    if (wp.expired()) //检查被引用的对象是否被删除了
    {
        cout << "shared_ptr is destroy" << endl;
    }
    else {
        cout << "shared_ptr no destroy" << endl;
    }
}

void test2() {
    cout << "---- test2 ------------------" << endl;
    weak_ptr<int> wp;
    shared_ptr<int> sp_ok;
    {
        shared_ptr<int> sp(new int(1));  //sp.use_count()==1
        wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
        sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
    }
    if (wp.expired()) {
        cout << "shared_ptr is destroy" << endl;
    }
    else {
        cout << "shared_ptr no destroy" << endl;
    }
}

int main(int argc, char *argv[]) {
    test1();
    test2();
    return 0;
}
输出:
---- test1 ------------------
shared_ptr is destroy
---- test2 ------------------
shared_ptr no destroy
2.使用lock保证数据安全

在这里插入图片描述

#include <iostream>
#include <memory>
#include <thread>
std::weak_ptr<int> gw;

void f2() {
    std::cout << "lock\n";
    auto spt = gw.lock();  // 锁好资源再去判断是否有效
    std::this_thread::sleep_for(std::chrono::seconds(2));
    if (gw.expired()) {
        std::cout << "gw Invalid, resource released\n";
    }
    else {
        std::cout << "gw Valid, *spt = " << *spt << std::endl;
    }
}

int main(int argc, char *argv[]) {
    {
        auto sp = std::make_shared<int>(42);
        gw = sp;
        std::thread([&]() {
            std::this_thread::sleep_for(std::chrono::seconds(1));

            std::cout << "sp reset\n";
            sp.reset();
        }).detach();

        f2();
    }
    f2();
    return 0;
    return 0;
}
输出结果:  得出结论sp被延迟释放了
lock
sp reset start
sp reset end
gw Valid, *spt = 42
lock
gw Invalid, resource released
3.解决循环引用问题
#include <iostream>
#include <memory>

using namespace std;
class A;
class B;

class A {
public:
    std::weak_ptr<B> bptr; 

    ~A() {
        cout << "A is deleted" << endl;
    }
};

class B {
public:
    std::weak_ptr<A> aptr;

    ~B() {
        cout << "B is deleted" << endl;
    }
};

int main() {
    {
        std::shared_ptr<A> ap(new A);
        std::shared_ptr<B> bp(new B);
        ap->bptr = bp;
        bp->aptr = ap;
    }
    cout << "main leave" << endl;  // 循环引用导致ap bp退出了作用域都没有析构
    return 0;
}
输出结果:
B is deleted
A is deleted
main leave
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

jbyyy、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值