一,智能指针
1.智能指针简介
智能指针是用法和行为类似于指针的类对象。
智能指针的底层对原始指针做了一定的封装。
智能指针除了像指针一样可以存储变量的地址,还提供了其他功能,比如可以管理动态内存分配,对引用进行计数等。
当智能指针所指向的变量离开了作用域或被重置时,智能指针会自动释放该变量所占用的堆内存资源。
至于为什么要引入智能指针,可以参考下面这段代码:
void func_1()
{
Sample* obj_one = new Sample();
if(something_wrong())
throw exception();
delete obj_one;
}
每次调用该函数的时候,都需要在堆中申请一段内存,然后在函数的最后释放该内存。但是当函数运行期间出现异常的时候,delete将不被执行,此时申请到的内存得不到释放,会发生内存泄露。智能指针由于是类对象,该类对象可以在析构的时候自动释放智能指针所指向的内存。因此,如果此时使用智能指针代替原始指针,可以不用手动调用"delete/delete []",智能指针指向的堆内存会自动被释放。
上述代码可以改写为:
void func_2()
{
auto obj_two = make_unique<Sample>();
if(something_wrong())
throw exception();
}
当unique_ptr实例离开作用域时(代码执行到了函数末尾,或者函数抛出异常),就会在其析构函数中自动释放obj_two对象所占有的内存资源。
标准库中提供了相应的类模板,它们可以将任何数据类型封装成智能指针,使用它们时,需要引入<memory>头文件。
智能指针常用的类模板有:
std::unique_ptr<T>
std::shared_ptr<T>
std::weak_ptr<T>
由上述的类模板可以生成三种类型的智能指针实例。这三种智能指针实例的区别在于,管理原始指针的方式不一样。
shared_ptr允许多个指针指向同一个变量。
unique_ptr则独占所指向的变量。
weak_ptr则指向shared_ptr所管理的变量。
2.智能指针的基础用法
1.智能指针的初始化
智能指针是基于类模板生成的,因此,要初始化一个智能指针,就必须声明指针所指向的数据类型,不然智能指针里面包含的原始指针是个空指针。
初始化方式一,在智能指针构造函数中new一个新对象。
struct C{
int a;
int b;
};
std::shared_ptr<C> p1(new C);
std::unique_ptr<int> p2(new int(40));
初始化方式二,采用make_shared函数(C++11标准)、make_unique函数(C++14标准)。
std::shared_ptr<int> p3 = std::make_shared<int>(15);
std::unique_ptr<int> p4 = std::make_unique<int>(10);
智能指针在初始化时,还可以用于指向动态分配的数组。
代码样例,创建长度为10的整型数组:
//方式一
auto Array_1 = make_unique<int[]>(10);
//方式二
std::unique_ptr<int[]> Array_2(new int[10]);
//类型+[],表示初始化指向数组的智能指针
//后面的具体用法和数组类似
Array_1[0] = 1;
Array_2[0] = 2;
注意,初始化weak_ptr需要用到shared_ptr。
代码样例:
auto sh_p = make_shared<int>(40);
weak_ptr<int> wp1(sh_p);
weak_ptr<int> wp2 = sh_p;
2.智能指针的解引用
智能指针的解引用操作与原始指针类似,可以调用"*"或"->"对智能指针进行解引用,访问分配到的堆内存地址。
但是weak_ptr不提供指针的解引用操作,即无法调用"*"或"->"获得weak_ptr所指向的变量。
完整C++代码实现:
#include <iostream>
#include <memory>
int main() {
struct C {
int a=1;
int b=2;
};
std::shared_ptr<C> p1(new C);
std::unique_ptr<int> p2(new int(40));
std::shared_ptr<int> p3 = std::make_shared<int>(15);
std::unique_ptr<int> p4 = std::make_unique<int>(10);
std::weak_ptr<int> p5 = p3;
std::cout << p1->a << std::endl;
std::cout << p1->b << std::endl;
std::cout << *p2 << std::endl;
std::cout << *p3 << std::endl;
std::cout << *p4 << std::endl;
std::cout << *p5.lock() << std::endl;
return 0;
}
运行结果:
1
2
40
15
10
15
3.unique_ptr智能指针
常用的成员函数:
get(): 返回指向变量的原始指针。
reset(): 重置智能指针,使它所持有的资源为空。
swap(): 交换两个智能指针所管理的资源。
release(): 返回指向变量的原始指针,并释放所有权。
用法说明:
reset()让unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原先占有的内存资源将被释放。
由于一个初始化后的unique_ptr独占了它所指向的变量,因此unique_ptr不支持普通的拷贝或赋值操作。
虽然不能拷贝或赋值unique_ptr,但可以通过调用release()/reset()函数将指针的所有权转移给另一个unique_ptr。
4.shared_ptr智能指针
常用的成员函数:
get(): 返回指向变量的原始指针。
reset(): 重置智能指针,使它所持有的资源为空。
swap(): 交换两个智能指针所管理的资源。
use_count(): 返回智能指针所指向变量的被引用数量。
unique(): 检查所指向的变量是否仅由当前shared_ptr的实例管理。
用法说明:
shared_ptr允许多个指针指向同一块堆内存。
shared_ptr提供了引用计数,监视当前变量正在被多少个指针实例所引用。
由于shared_ptr存在引用计数,仅在最后一个引用被销毁或重置时,该智能指针才会释放持有的内存资源。。
shared_ptr可被以下函数强制转换:
const_pointer_cast()
dynamic_pointer_cast()
static_pointer_cast()
reinterpret_pointer_cast() (C++17标准引入)
如图所示,指针p1、p2指向同一块内存地址。
5.weak_ptr智能指针
常用的成员函数:
reset(): 重置智能指针,使它所持有的资源为空。
swap(): 交换两个智能指针所管理的资源。
expired(): 检查weak_ptr所指向的资源是否有效,返回true的时候,垃圾回收进程就会清除该指针所指向的内存资源。
use_count(): 返回智能指针所指向shared_ptr的数量。
lock(): 获取weak_ptr所指向的shared_ptr实例。
用法说明:
weak_ptr不占有内存资源,但是可以指向由shared_ptr管理的内存资源。
当weak_ptr指向shared_ptr时,是弱共享shared_ptr,并不会使shared_ptr的引用计数增加。
weak_ptr的出现可以帮助开发者解决智能指针使用期间发生的"循环引用"问题。
完整C++代码实现:
#include <iostream>
#include <memory>
using namespace std;
void Check(weak_ptr<int>& wp) {
shared_ptr<int> sp = wp.lock(); //获得shared_ptr<int>实例
if (sp != nullptr)
cout << "still " << *sp << endl;
else
cout << "pointer is invalid." << endl;
}
int main() {
shared_ptr<int> sp1(new int(40));
shared_ptr<int> sp2 = sp1;
weak_ptr<int> wp = sp1; //wp指向shared_ptr<int>实例
cout << *sp1 << endl;
cout << *sp2 << endl;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
Check(wp);
sp1.reset();
/*
cout << sp1 << endl;
返回:00000000
*/
cout << *sp2 << endl;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
cout << sp2.unique() << endl;
Check(wp);
sp2.reset();
cout << sp2.use_count() << endl;
Check(wp);
return 0;
}
运行结果:
40
40
2
2
still 40
40
0
1
1
still 40
0
pointer is invalid.
6.智能指针的复制和移动
unique_ptr不支持复制、赋值等操作,它只能被移动,而移动操作经常借助std::move函数来实现。
std::move可以把一个智能指针所占有的资源转移给另一个智能指针。
shared_ptr包含一个显式的构造函数,可用于将右值unique_ptr转换为shared_ptr。转换成功以后,shared_ptr将接管unique_ptr所占有的所有资源。因此,如果unique_ptr为右值(可以粗略理解为,位于赋值符号的右边)的时候,可以将其赋值给shared_ptr。
复制操作会让shared_ptr所指向变量的引用计数加1,而移动操作不会让shared_ptr所指向变量的引用计数加1。
1.unique_ptr转shared_ptr代码样例:
std::shared_ptr<int> p1 = std::make_unique<int>(66);
完整C++代码实现:
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p1 = std::make_unique<int>(66);
std::cout << *p1 << std::endl;
std::cout << p1.use_count() << std::endl;
return 0;
}
运行结果:
66
1
2.移动操作代码样例:
//p1指向该int对象
shared_ptr<int> p1(new int(40));
//移动构造p2
//p1指向的内存为空,p2指向该int对象
shared_ptr<int> p2(std::move(p1));
//p2指向的内存为空,p3指向该int对象
shared_ptr<int> p3;
p3 = std::move(p2);
//整个过程中,int对象的引用计数保持在1
完整C++代码实现:
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p1(new int(40));
std::shared_ptr<int> p2(std::move(p1));
std::shared_ptr<int> p3;
p3 = std::move(p2);
std::cout << p3.use_count() << std::endl;
std::shared_ptr<int> p4;
std::shared_ptr<int> p5;
p4 = p3;
p5 = p3;
std::cout << p3.use_count() << std::endl;
p3.reset();
p3 = std::make_unique<int>(10);
std::cout << p3.use_count() << std::endl;
return 0;
}
运行结果:
1
3
1
7.关于new/delete的补充说明
显式地调用new和delete不仅会增加代码的复杂度,还会引发内存泄露等风险。
如果必须要使用new/delete,可以考虑以下措施来避免:
1.尽可能使用栈内存
栈内存不会造成内存泄露,且资源一旦超出栈的使用范围就会被销毁。
2.使用make functions在堆上分配资源
例如,使用std::make_unique<T>或std::make_shared<T>来实例化资源,然后将它包装成一个资源管理对象去管理资源以及智能指针。
3.尽量使用容器(标准库中的容器,Boost中的容器等)
容器会对其元素进行存储空间的管理,这些官方容器都实现了自己的内存管理逻辑,避免内存出问题。
至于为何要采用上述规则,后面会新增动态内存分配和右值引用等相关章节来详细说明。
二,参考阅读
《C++ Primer》
《C++代码整洁之道》
《C++高级编程》
《C++新经典》
https://www.apiref.com/cpp-zh/cpp/memory.html
https://learn.microsoft.com/zh-cn/cpp/cpp/smart-pointers-modern-cpp?view=msvc-170
标签:std,C++,智能,shared,unique,ptr,指针 From: https://blog.csdn.net/CoderZZ_2024/article/details/136792915