[modern c++] 的智能指针 shared_ptr / unique_ptr / weak_ptr

本文介绍C++中智能指针shared_ptr、unique_ptr和weak_ptr的基本用法及注意事项,包括使用make_shared创建对象、计数器原理、weak_ptr的作用及如何避免悬空指针。

前言:

  1. 使用 普通指针/其他已存在的智能指针/其他已存在的普通指针,对当前创建的智能指针进行初始化。            (创建 指针 指向当前已有内存)
  2. 使用 make_shared 创建全新的内存区,然后创建一个全新的智能指针指向它。     (创建 内存空间 和 指针)
  3. 使用 智能指针的 reset 方法来进行 智能指针的 重定向 和 释放。              (重定向 和 销毁)

std::shared_ptr - cppreference.com

std::unique_ptr - cppreference.com




智能指针

智能指针有两种: shared_ptr  和  unique_ptr,这两种指针都是模板类,原型为   xxx_ptr<T>

(!)注:智能指针是用来管理堆内存的,不是作为指针使用的。

(!)智能指针是使用delete来释放内存,所以释放内存时的特性和delete一样。

int i = 100;    //或者其他类,比如 classA a;
shared_ptr<int> sp  = make_shared<int>(i);

上述语句实际上是使用i的值作为新分配内存的初始值。sp并没有指向i,而是指向了新分配的堆,这个堆存放int型的数值100。上述语句不能算错误,但是需要理解  make_shared 动作实际上是取堆里申请了内存的。

可以这样理解,make_shared 相当于 new ,尖括号指定需要分配内存的类型名,小括号指定作为拷贝构造传递给类型名的值,如果不指定,那么使用类型的默认构造。

classA *pa = new classA();
shared_ptr<classA> sp = make_shred<classA>(*pa);    

上述代码中,使用classA的拷贝构造在堆里创建一个新的对象,而不是使用pa指向的对象。




应用场景


(!!!)使用动态内存的一个常见原因是:允许多个对象共享相同的状态。

(!!!)为什么不做成static的?
主要因为static数据存放在.bss区,无法手动释放,在程序运行时就载入,而这部分的可用空间是有限而狭小的。如果我们想要使用堆栈这样宽敞的空间来模拟static成员,此时就可以使用共享指针管理的动态内存(堆)来实现,实现一个仅在大家都不在需要时才会释放的堆区域。

一个典型的场景:

class A{

public:
    vector<classB> m_B_list;        //非常庞大的一个列表(实际这里可以使vector指针,这里只是做一个描述)

}

如果A有N多个实例,问题就出现了,每个A实例都要有一个m_B_list,首先很吃内存,其次各个A实例之间需要实施互相同步数据,从而保证这个vector对于所有A来说,都是一样的。这是很棘手和难处理的:

  • 可以让vector是static的,但是这样比较占用static区域,而且无法释放。      (不可选)
  • 可以简单粗暴,让所有A在更改vector之后,通知其他A跟新自己的vector,这样既 占内存,又 占CPU。         (不可选)
  • 创建一个command管理类,让command管理类来存放vector,并提供增删改查接口给所有A使用,这样保证了效率,一定程度上节约了内存。但是,需要新增一个类,而这个类的存在感很低,因为它只是来做一个中转。                    (不是最优解)
  • 不创建command管理类,但是在外部区域创建vector,然后让A都能访问,这样有风险,如果某个A出于“某种原因”把vector给删掉了。那么其他A在访问的时候将访问到空指针,或者直接崩溃。这种情况特别是在A都存放vector指针是明显,因为析构时会释放成员变量。(不是最优解)
  • 使用shared_ptr,vector不使用new创建,而是使用make_shared,在A构造的时候创建。但是需要注意各个A运行在不同的线程下,同时操作vector可能产生竞争。 (最优解)



new出来的内存不能直接赋值

上面提到了智能指针的使用方法,通过make_shared来分配内存,但是如果想使用new来分配内存,然后交由智能指针管理,该如何操作???

首先,智能指针的构造函数时explict的,即不接受隐式转换,即必须是指定类型,故先new再通过赋值构造是行不通的

可以使用拷贝构造函数来实现:

shared_ptr<classA> p_A = new classA();    //错误
shared_ptr<classA> p_A(new classA());    //正确

同理,使用普通指针给智能指针赋值也是行不通的:

shared_ptr<classA> clone(){        //错误
    return new classA();        //返回值不接收这种隐式转换
}

shared_ptr<classA> clone(){                //正确
    return shared_ptr<classA>(new classA());    //返回值也是智能指针
}

上面提到的使用普通指针来初始化智能指针的场景,要求普通指针指向的必须是动态内存(即new分配的),静态内存不行,
        比如:
            int i = 10;
            const int j = 10;
            int *p = &i;
            const int *pj = &j;
            shared_ptr<int> sp(p);            //不能把栈给智能指针
            shared_ptr<int> spp(pj);        //不能把data区给智能指针




weak_ptr

weak_ptr 是给 shared_ptr做补充的,把weak_ptr增加到shared_ptr指向的对象上,不会增加计数,单当shared_ptr全部释放完以后,weak_ptr指向的内容也将不存在。 weak_ptr的使用必须 经过自己的lock()方法,这个方法会peek对象是否存在,存在返回true,否则false,用完释放weak_ptr亦不会导致计数减少。weak_prt可以理解为shared_ptr的监视器。仅仅用来peek数据。

为什么要使用weak_ptr,weak_ptr 和 raw pointer 在使用上有什么不同?

The fundamental conceptual difference between a naked pointer and a weak_ptr is that if the object pointed to is destroyed, the naked pointer won't tell you about it. This is called a dangling pointer: a pointer to an object that doesn't exist. They're generally hard to track down.

The weak_ptr will. In order to use a weak_ptr, you must first convert it into a shared_ptr. And if that shared_ptr doesn't point to anything, then the object was deleted.

For example:

#include <iostream>
#include <memory>

std::weak_ptr<int> wp;

void test()
{
    auto spt = wp.lock(); // Has to be copied into a shared_ptr before usage
    if (spt) {
        std::cout << *spt << "\n";
    } else {
        std::cout << "wp is expired\n";
    }
}

int main()
{
    {
        auto sp = std::make_shared<int>(42);
        wp = sp;
        test();
    }
    test();
}

Output

42
wp is expired



成员函数里把this指针当作shared_ptr传递

有这样一种场景,假设播放器里面包含video track 和 audio track,如果希望在 video track 和 audio track里面使用 player,这就构成了双向指针指向问题,简单的处理办法就是直接把this指针传递给 video trakc 和 audio track,这样做有一个弊端就是 video track 和 audio track 无法及时感知到 player 指针是否还有效,所以需要 player 提供访问 this 指针的锁来进行同步。

modern c++ 提供了一个共享 this 指针的办法,那就是通过enable_shared_from_this来声明某个类支持把 this 指针转换成 shared_ptr。在使用的时候需要通过 shared_from_this() 来生成 this 指针的 shared_ptr 对象,这会强制要求 this 指针指向的对象增加一个引用计数。

class Player:public std::enable_shared_from_this<Player>
{

}

std::unique_ptr<Track> track = TrackFactory::createTrack(shared_from_this());

注意:千万不要使用 std::shared_ptr<LocalFilePlayer>(this) 创建 this 指针的 shared_ptr,这不会让 this 对象保存一次计数,也就是说如果最底层是 weak_ptr 来接受 std::shared_ptr<LocalFilePlayer>(this) , 那么就会导致计数为0。

错误用法:

std::unique_ptr<Track> track = TrackFactory::createTrack(std::shared_ptr<LocalFilePlayer>(this))

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值