前言:
- 使用 普通指针/其他已存在的智能指针/其他已存在的普通指针,对当前创建的智能指针进行初始化。 (创建 指针 指向当前已有内存)
- 使用 make_shared 创建全新的内存区,然后创建一个全新的智能指针指向它。 (创建 内存空间 和 指针)
- 使用 智能指针的 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_ptris 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_ptrwill. In order to use aweak_ptr, you must first convert it into ashared_ptr. And if thatshared_ptrdoesn'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))
本文介绍C++中智能指针shared_ptr、unique_ptr和weak_ptr的基本用法及注意事项,包括使用make_shared创建对象、计数器原理、weak_ptr的作用及如何避免悬空指针。
3995

被折叠的 条评论
为什么被折叠?



