1. 智能指针分类
- 共享型智能指针(shared_ptr):同一块堆内存可以被多个shared_ptr拥有。
- 独享型智能指针(unique_ptr):同一块堆内存只能被一个unique_ptr拥有。
- 弱引用型智能指针(weak_ptr):也是一种共享型智能指针,算是对共享型智能指针的补充。
2. shared_ptr
2.1 工作原理
(1)我们在分配内存时,堆上的内存必须通过栈上的内存来寻址,即通过栈上的指针来寻址堆内存,也是唯一的方式。
(2)在堆内存中添加一个引用计数,记录有几个指针指向它,当引用计数为0时,操作系统会自动释放该内存。
2.2 常用操作
(1)初始化
#include<iostream>
#include<memory>
void testPtrMian()
{
/*
* 方法一
* 使用new进行初始化,不会内存泄漏但不建议
**/
std::shared_ptr<int> sharedI(new int(100));
/*
* 方法二
* 使用make_shared函数进行初始化
**/
std::shared_ptr<int> sharedI = std::make_shared<int>(100);
/*
* 方法三
* 使用普通指针进行初始化
**/
nt* pi = new int(100);
std::shared_ptr<int> sharedI(pi);
delete(pi); // 错误,内存被重复释放
/*
* 方法四
* 使用复制构造函数进行初始化
**/
std::shared_ptr<int> sharedI = std::make_shared<int>(100);
std::shared_ptr<int> sharedI2(sharedI);
}
(2)引用计数
智能指针是通过引用计数来判断内存的释放时机,使用use_count()函数可以得到shared_ptr的引用计数。
(3)share_ptr的操作和普通指针完全一致,除了数组支持不好。
(4)常用函数
unique():判断该shared_ptr对象是否独占,独占返回true,否则返回false。
reset():无参数,即释放控制权。有参数,即释放控制权并指向新的堆内存。
get():千万不要使用!如果要用也不要delete返回的指针。返回的是能操作的指针。
swap():交换两个智能指针指向的堆内存。
#include<iostream>
#include<memory>
int testPtrMian()
{
std::shared_ptr<int> sharedI(new int(100));
std::cout << sharedI.use_count() << std::endl; // 输出:1
std::cout << sharedI.unique() << std::endl; // 输出:true 即 1
std::shared_ptr<int> sharedI2(sharedI);
std::cout << sharedI.use_count() << std::endl; // 输出:2
std::cout << sharedI.unique() << std::endl; // 输出:false 即 0
sharedI2.reset(); // 释放控制权,引用计数-1
sharedI2.reset(new int(1000)); // 指向新的堆内存
sharedI.swap(sharedI2); // *sharedI = 1000,*sharedI2 = 100,也可以使用std::swap(sharedI, sharedI2)
std::cout << sharedI.use_count() << std::endl; // 输出:1
std::cout << sharedI.unique() << std::endl; // 输出:true 即 1
return 0;
}
(5)智能指针创建数组
int testPtrMian()
{
std::shared_ptr<int> sharedI(new int[100]());
std::cout << sharedI[20] << std::endl; // 错误,不能直接做数组操作
std::cout << sharedI.get()[20] << std::endl; // 正确
return 0;
}
(6)智能指针传参
作为函数传参时,与普通参数一样,按值传递的方式就可以。这是因为shared_ptr的大小固定为8或16个字节(两倍指针大小,32位系统指针为4个字节,64位系统指针为8个字节,即shared_ptr中有两个指针),所以直接值传递就可以了。
void test(std::shared_ptr<int> testSharedPtr) {}
int testPtrMian()
{
std::shared_ptr<int> sharedI = std::make_shared<int>(100);
test(sharedI);
return 0;
}
3. weak_ptr
weak_ptr主要用来弥补shared_ptr的循环引用问题。
3.1 循环引用问题
#include<iostream>
#include<memory>
class A
{
public:
std::shared_ptr<B> sharedB;
~A() {} // 只有析构函数执行后,才能释放成员变量
};
class B
{
public:
std::shared_ptr<A> sharedA;
~B() {} // 只有析构函数执行后,才能释放成员变量
};
void testPtrMian()
{
std::shared_ptr<A> sharedPtrA = std::make_shared<A>(); // 指向A的计数为1
std::shared_ptr<B> sharedPtrB = std::make_shared<B>(); // 指向B的计数为1
sharedPtrA->sharedB = sharedPtrB; // 指向B的计数为2
sharedPtrB->sharedA = sharedPtrA; // 指向A的计数为2
}
上述代码,testPtrMian()结束后,释放sharedPtrA,sharedPtrB,此时指向A的计数为1,指向B的计数为1,因为A、B存在指向,所以不会调用析构函数,更不会释放成员变量。死锁发生,这就是循环引用问题。
3.2 weak_ptr的使用
将其中一个替换成weak_ptr则不会有内存不释放的问题,因为weak_ptr不影响引用计数,同时weak_ptr需要绑定到shared_ptr上次才能使用。
#include<iostream>
#include<memory>
class A
{
public:
std::weak_ptr<B> weakB;
~A() {}
};
class B
{
public:
std::shared_ptr<A> sharedA;
~B() {}
};
void testPtrMian()
{
std::shared_ptr<A> sharedPtrA = std::make_shared<A>(); // 指向A的计数为1
std::shared_ptr<B> sharedPtrB = std::make_shared<B>(); // 指向B的计数为1
sharedPtrA->weakB = sharedPtrB; // 指向B的计数为1,weak_ptr不影响引用计数
sharedPtrB->sharedA = sharedPtrA; // 指向A的计数为2
}
4. unique_ptr
我们在使用智能指针时,一般优先考虑unique_ptr,因为消耗更小,如果内存需要共享,才使用shared_ptr。
(1)初始化方法与shared_ptr基本一致。
(2)注意:unique_ptr禁止复制构造初始化,也禁止使用赋值运算符。
(3)unique_ptr允许移动构造,移动赋值。移动语义代表之前的对象已经失去意义。
(4)reset()函数与shared_ptr完全一样。
(5)unique_ptr与普通指针大小相同,因此比shared_ptr小一倍。
void testPtrMian()
{
std::unique_ptr<int> uniqueI = std::make_unique<int>(100);
std::unique_ptr<int> uniqueI2 = uniqueI; // 错误,不能使用赋值运算符
std::unique_ptr<int> uniqueI2(uniqueI); // 错误,不能使用复制构造
std::unique_ptr<int> uniqueI2(std::move(uniqueI)); // 正确,可以使用移动构造
}
(6)将unique_ptr对象转化为shared_ptr对象
注意:shared_ptr对象无法转化为unique_ptr对象。
void test(std::unique_ptr<int> uniqueI)
{
// 转换时,先将unique_ptr转化为右值,同时保证之后不再使用unique_ptr对象
std::shared_ptr<int> sharedI(std::move(uniqueI));
}
5. 智能指针的使用范围
5.1 不能使用智能指针的情况
必须使用C语言的指针:
(1)网络传输函数,如#include<WinSock2.h>头文件下的send(),recv()函数,只能使用C语言的指针。
(2)C语言的文件操作部分,不过C+已经有了替代品。
5.2 使用什么智能指针
(1)优先使用unique_ptr,需要共享时再使用shared_ptr。
(2)当使用shared_ptr时,遇到了循环引用问题,考虑联合weak_ptr一起使用。