目录
为什么需要智能指针
智能指针主要解决以下问题:
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;
使用例程
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