C++智能指针
C++智能指针
在C++中,内存的分配和释放都是由开发者手动实现的。这种方式虽然很灵活,但也十分容易出错,比如忘记释放内存或释放了已经释放的内存等。
动态内存的管理是通过一对运算符来完成的:
new
: 在动态内存中为对象分配空间,并且返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete
: 接受一个动态对象的指针,销毁该对象,并且释放与之关联的内存。
动态内存的使用很容易出问题,因为确保在正确的时间释放内存是极其困难的:
- 有时我们会忘记释放内存,在这种情况下会产生内存泄漏;
- 有时在尚有指针引用内存的情况下我们就释放了它,这种情况下会产生引用非法内存的指针。
为了避免这些问题,C++引入了智能指针这一概念。智能指针是一种类,它在析构时自动释放所管理的对象所占用的内存。这样,程序员就不需要手动管理内存,减少了出错的可能性。
C++中有三种智能指针:unique_ptr
、shared_ptr
和weak_ptr
。
unique_ptr
unique_ptr
是一个独占式的指针对象,不能共享所有权。也不允许复制拷贝,在任何时间、资源只能被一个指针占有
当unique_ptr
被销毁时,或者离开作用域,它所管理的对象的内存也会被自动释放。
简单示例
std::unique_ptr<int>p1(new int(5));
std::unique_ptr<int>p2=p1; // 编译会出错
std::unique_ptr<int>p3=std::move(p1); // 转移所有权, 现在那块内存归p3所有, p1成为无效的针.
p3.reset(); //释放内存.
p1.reset(); //无效
构造方法
#include <iostream>
#include <memory>
int main () {
std::default_delete<int> d;
std::unique_ptr<int> u1; // u1 指针
std::unique_ptr<int> u2 (nullptr); // u2 指针
std::unique_ptr<int> u3 (new int);
std::unique_ptr<int> u4 (new int, d);
std::unique_ptr<int> u5 (new int, std::default_delete<int>());
std::unique_ptr<int> u6 (std::move(u5));
std::unique_ptr<int> u7 (std::move(u6));
std::unique_ptr<int> u8 (std::auto_ptr<int>(new int));
std::cout << "u1: " << (u1?"not null":"null") << '\n';
std::cout << "u2: " << (u2?"not null":"null") << '\n';
std::cout << "u3: " << (u3?"not null":"null") << '\n';
std::cout << "u4: " << (u4?"not null":"null") << '\n';
std::cout << "u5: " << (u5?"not null":"null") << '\n';
std::cout << "u6: " << (u6?"not null":"null") << '\n';
std::cout << "u7: " << (u7?"not null":"null") << '\n';
std::cout << "u8: " << (u8?"not null":"null") << '\n';
return 0;
}
// --------------------------------------------------------------------------
u1: null
u2: null
u3: not null
u4: not null
u5: null
u6: null
u7: not null
u8: not null
释放和重置
释放 std::unique_ptr::release
释放并不会销毁其指向的对象,而且将其指向的对象释放出去
重置 std::unique_ptr::reset
#include <iostream>
#include <memory>
void Test1(){
std::unique_ptr<int> auto_pointer (new int); // 初始化
int * manual_pointer;
*auto_pointer=10;
manual_pointer = auto_pointer.release(); // 释放
// (auto_pointer is now empty)
std::cout << "manual_pointer points to " << *manual_pointer << '\n';
std::cout << "auto_pointer :" <<*auto_pointer << '\n';
delete manual_pointer;
}
int main () {
Test1();
return 0;
}
// ------------------------------------------------------------------
manual_pointer points to 10
auto_pointer :
#include <iostream>
#include <memory>
void Test2(){
std::unique_ptr<int> up; // empty
up.reset (new int); // 重置
*up=5;
std::cout << "*up:" << *up << '\n';
up.reset (new int); // 重置
*up=10;
std::cout << "*up:" << *up << '\n';
up.reset(); // deletes managed object
}
int main () {
Test2();
return 0;
}
// -------------------------------------------------------------------
*up:5
*up:10
参考 https://blog.csdn.net/fuhanghang/article/details/113928128z
#include <iostream>
#include <memory>
using namespace std;
int main() {
// 使用unique_ptr管理int类型的对象
unique_ptr<int> up1(new int(10));
cout << "up1: " << *up1 << std::endl;
// 使用make_unique函数创建unique_ptr对象
auto up2 = make_unique<int>(20);
cout << "up2: " << *up2 << std::endl;
// unique_ptr可以通过std::move()转移所有权
unique_ptr<int> up3 = std::move(up1);
cout << "up3: " << *up3 << std::endl;
return 0;
}
shared_ptr
shared_ptr
是一个共享所有权的智能指针,可以有多个shared_ptr
指向同一个对象。
每当一个shared_ptr
被销毁时,它所管理的对象的引用计数会减1。当引用计数为0时,对象的内存才会被自动释放。
构造方法
#include <iostream>
#include <memory>
///1.构造函数初始化
int main(){
std::shared_ptr<int> p0(nullptr);
std::cout << "p0.use_count=" << p0.use_count() << '\n'; //空指针 计数=0
//构造函数初始化,指向一个存有5这个int类型数据的堆内存空间
std::shared_ptr<int> p1(new int(5));
std::cout << "p1=" << *p1 << '\n'; //p1=5
std::cout << "p1.use_count=" << p1.use_count() << '\n'; //1
std::shared_ptr<int> p2(p1); //p1和p2都指向那个存有int型5的堆内存空间,堆内存的引用次数会加1
std::cout << "p1.use_count=" << p1.use_count() << '\n'; //2
std::cout << "p2.use_count=" << p2.use_count() << '\n'; //2
std::shared_ptr<int> p3=p0; //p0为空,则p3也为空,其引用计数依然为0
std::cout << "p3.use_count=" << p3.use_count() << '\n'; //0
//补充
//可以把原始指针传参构造shared_ptr对象,此时原始指针没有new或没有初始化赋值,use_count都是1
int *p11 = new int; // 原始指针 计数为1
//如果没有=new int,下面p12.use_count还是1,但执行打印时会crash
std::shared_ptr<int> p12(p11);
std::cout << "p12.use_count=",p12.use_count() << '\n'; //1
std::cout << "*p12=" << *p12 << '\n';//*p12=1345903632(原始指针new了,但没有初始化,则打印未知值)
}
// ------------------------------------------------------------------------
p0.use_count=0
p1=5
p1.use_count=1
p1.use_count=2
p2.use_count=2
p3.use_count=0
p12.use_count=*p12=42083472
//使用make_shared. 更高效
auto p1 = std::make_shared<int>(10);
auto p2 = std::make_shared<string>(10,"s");
auto p3 = std::make_shared<Struct>();
常用函数
get()函数,表示返回当前存储的原始指针
shared_ptr<T> ptr(new T());
T *p = ptr.get(); // 获得传统 C 指针
use_count()函数,表示当前引用计数
shared_ptr<T> a(new T());
a.use_count(); //获取当前的引用计数
reset()函数,表示重置当前存储的指针
shared_ptr<T> a(new T());
a.reset(); // 此后 a 原先所指的对象会被销毁,并且 a 会变成 NULL
构造重置和析构
#include <iostream>
#include <memory>
class book
{
public:
book(int v) { //构造函数
value = v;
std::cout << "cons book value=" <<value<< std::endl;
}
~book() {//析构函数
std::cout << "desc book value=" <<value<< std::endl;
}
int value;
};
int main()
{
std::shared_ptr<book> b1(new book(100)); //cons book value=100
std::shared_ptr<book> b2(b1); //只增加了引用计数,没有新增构造book,use_count=2
b2.reset(new book(200)); //cons book value=200(新构造book200,原book100计数变为1)
b1.reset(new book(300)); //cons book value=300
desc book value=100(新构造book300,原book100引用计数变为0,执行析构)
return 0;
}
获得原始指针
std::shared_ptr<int> p10(new int(300));
int *pn = p10.get();
std::cout << "pn=",*pn << '\n'; //pn=300
struct ClassWrapper {
ClassWrapper() {
cout << "construct" << endl;
data = new int[10];
}
~ClassWrapper() {
cout << "deconstruct" << endl;
if (data != nullptr) {
delete[] data;
}
}
void Print() {
cout << "print" << endl;
}
int* data;
};
void Func(std::shared_ptr<ClassWrapper> ptr) {
ptr->Print();
}
int main() {
auto smart_ptr = std::make_shared<ClassWrapper>();
auto ptr2 = smart_ptr; // 引用计数+1
ptr2->Print();
Func(smart_ptr); // 引用计数+1
smart_ptr->Print();
ClassWrapper *p = smart_ptr.get(); // 可以通过get获取裸指针
p->Print();
return 0;
}
注意事项
- 不能使用原始指针初始化多个shared_ptr,会出现double_free导致程序崩溃
- 不允许以暴露裸漏的指针进行赋值
- shared_ptr不是线程安全;
在多线程下,不能保证new出来一个对象一定能被放入shared_ptr中,也不能保证智能指针管理的引用计数的正确性;
- 通过shared_from_this()返回this指针,不要把this指针作为shared_ptr返回出来,因为this指针本质就是裸指针,通过this返回可能 会导致重复析构,不能把this指针交给智能指针管理。
- 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。
weak_ptr
weak_ptr
是一个弱引用的智能指针,它与shared_ptr
搭配使用。
借助 weak_ptr
类型指针可以获取 shared_ptr
指针的一些状态信息,比如有多少指向相同的 shared_ptr
指针。
weak_ptr
不会增加所管理的对象的引用计数,因此它不会影响对象的生命周期。
可以通过weak_ptr
的lock()
成员函数来获取一个指向所管理的对象的shared_ptr
。
weak_ptr
是用来监视shared_ptr的生命周期,它不管理shared_ptr内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。
构造方法
#include <iostream>
using namespace std;
int main()
{
auto sp = make_shared<int>(100); // 强引用计数从0变1
weak_ptr<int> wp(sp); // wp弱共享sp,强引用计数仍为1,弱引用计数从0变1
return 0;
}
常用函数
.use_count() 获取当前'强引用'所管理的资源的引用计数的个数
.expired() 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false
.reset() 在弱引用weak_ptr中,将该弱引用指针设置为空nullptr,不影响指向该对象的强引用数量
.lock() 判断weak_ptr所指向的shared_ptr对象是否存在****。若存在,则这个lock方法会返回一个指向该对象的shared_ptr指针;若它所指向的这个shared_ptr对象不存在,则lock()函数会返回一个空的shared_ptr
weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数
#include <memory>
#include <iostream>
int main(){
auto pi1 = std::make_shared<int>(111);
auto pi2(pi1);//pi2是一个shared_ptr
std::weak_ptr<int> piw(pi1);
int n = piw.use_count();
std::cout << "n = " << n << std::endl;
pi1.reset();
pi2.reset();
if (piw.expired()) {
std::cout << "weak_ptr is release" << std::endl;
}
auto ps1 = std::make_shared<int>(42);
std::weak_ptr<int> psw;
psw = ps1; //用shared_ptr给weak_ptr赋值
if (!psw.expired()) {
auto ps2 = psw.lock(); //.lock()方法返回一个shared_ptr,并且此时强引用计数为2
if (ps2) {
//ps2非空,表明psw所指向的shared_ptr所管理的对象此时没有expire
std::cout << "ps2 is exist" << std::endl;
}
else {
std::cout << "ps2 not exist" << std::endl;
}
}
return 0;
}
// ---------------------------------------------------------------------------------
n = 2
weak_ptr is release
ps2 is exist
#include <memory>
#include <iostream>
int main(){
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10); //创建一个智能指针
std::cout << "sh_ptr.use_count()=" << sh_ptr.use_count() << "\n"; //sh_ptr.use_count()=1
std::weak_ptr<int> wp(sh_ptr); //构造 weak_ptr,不会增加智能指针的引用计数
std::cout << "sh_ptr.use_count()=" << sh_ptr.use_count() << "\n"; //sh_ptr.use_count()=1
std::cout << "wp.use_count()=" << wp.use_count() << "\n"; // wp.use_count() =1
if(!wp.expired()){ // 检查sh_ptr是否还有效
std::shared_ptr<int> sh_ptr2 = wp.lock(); //使用 weak_ptr 构造一个智能指针,引用计数+1
*sh_ptr = 100;
std::cout << "wp.use_count()=" << wp.use_count() << "\n"; // wp.use_count() =2
}
return 0;
}
// ----------------------------------------------------------------------
sh_ptr.use_count()=1
sh_ptr.use_count()=1
wp.use_count()=1
wp.use_count()=2
解决循环引用
weak_ptr的一个作用是解决share_ptr的循环引用问题
#include <memory>
#include <iostream>
class BB;
class AA
{
public:
AA() { std::cout << "AA::AA() called\n"; }
~AA() { std::cout << "AA::~AA() called\n"; }
std::shared_ptr<BB> m_bb_ptr; //正确用法是使用weak_ptr,可以正常析构
};
class BB
{
public:
BB() { std::cout <<"BB::BB() called\n" ; }
~BB() { std::cout <<"BB::~BB() called\n" ; }
std::shared_ptr<AA> m_aa_ptr; //正确用法是使用weak_ptr,可以正常析构
};
int main(){
std::shared_ptr<AA> ptr_a(new AA);
std::shared_ptr<BB> ptr_b(new BB);
std::cout << "ptr_a use_count:" << ptr_a.use_count() << "\n";
std::cout << "ptr_b use_count:" << ptr_b.use_count() << "\n";
//下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构
ptr_a->m_bb_ptr = ptr_b;
ptr_b->m_aa_ptr = ptr_a;
std::cout << "ptr_a use_count:" << ptr_a.use_count() << "\n";
std::cout << "ptr_b use_count:" << ptr_b.use_count() << "\n";
return 0;
}
// --------------------------------------------------------------------------------------
AA::AA() called
BB::BB() called
ptr_a use_count:1
ptr_b use_count:1
ptr_a use_count:2
ptr_b use_count:2
循环引用导致ptr_a和ptr_b的引用计数都为2
在离开作用域之后,ptr_a和ptr_b的引用计数只减为1,而没有减为0,导致两个指针都不会被析构,产生内存泄漏。
#include <memory>
#include <iostream>
class BB;
class AA
{
public:
AA() { std::cout << "AA::AA() called\n"; }
~AA() { std::cout << "AA::~AA() called\n"; }
std::weak_ptr<BB> m_bb_ptr; //正确用法是使用weak_ptr,可以正常析构
};
class BB
{
public:
BB() { std::cout <<"BB::BB() called\n" ; }
~BB() { std::cout <<"BB::~BB() called\n" ; }
std::weak_ptr<AA> m_aa_ptr; //正确用法是使用weak_ptr,可以正常析构
};
int main(){
std::shared_ptr<AA> ptr_a(new AA);
std::shared_ptr<BB> ptr_b(new BB);
std::cout << "ptr_a use_count:" << ptr_a.use_count() << "\n";
std::cout << "ptr_b use_count:" << ptr_b.use_count() << "\n";
//下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构
ptr_a->m_bb_ptr = ptr_b;
ptr_b->m_aa_ptr = ptr_a;
std::cout << "ptr_a use_count:" << ptr_a.use_count() << "\n";
std::cout << "ptr_b use_count:" << ptr_b.use_count() << "\n";
return 0;
}
// ---------------------------------------------------------------------
AA::AA() called
BB::BB() called
ptr_a use_count:1
ptr_b use_count:1
ptr_a use_count:1
ptr_b use_count:1
BB::~BB() called
AA::~AA() called
参考资料
https://juejin.cn/post/7078508447053905933
https://juejin.cn/post/7243082572538904613?searchId=20231228173634B4D68943E877C367A478