33、智能指针
在学习智能指针之前,我们需要了解RAII
的概念。
为什么需要智能指针?
C++
中申请内存一般使用new
,释放内存一般使用delete
,写程序的时候很有可能你写了new
,但是没有进行delete
,特别是复杂的多线程****程序。
那有什么办法可以自动释放你申请的内存?答案就是使用智能指针。
智能指针是在C++11
才引入到标准中的。
C++11
中引入了三种智能指针:
std::shared_ptr
std::weak_ptr
std::unique_ptr
shared_ptr
shared_ptr
使用了引用计数,每一个shared_ptr
的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr
析构的时候,内存才会释放。
使用方法如下:
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;
}
智能指针还可以**自定义删除器,**在引用计数为0的时候自动调用删除器来释放对象的内存,代码如下:
std::shared_ptr<int> ptr(new int, [](int *p){ delete p; });
关于**shared_ptr
**有几点需要注意:
• 不要用一个裸指针初始化多个shared_ptr
,会出现double_free
导致程序崩溃
• 通过shared_from_this()
返回this
指针,不要把this
指针作为shared_ptr
返回出来,因为this
指针本质就是裸指针,通过this
返回可能 会导致重复析构,不能把this
指针交给智能指针管理。
class A {
shared_ptr<A> GetSelf() {
return shared_from_this();
// return shared_ptr<A>(this); 错误,会导致double free
}
};
- 尽量使用
make_shared
,少用new
。为什么呢?可以看这个https://articles.zsxq.com/id_mfs6nj9rj4xg.html - 不要
delete
get()返回来的裸指针。 - 不是
new
出来的空间要自定义删除器。 - 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。
using namespace std;
struct A;
struct B;
struct A {
std::shared_ptr<B> bptr;
~A() { cout << "A delete" << endl; }
};
struct B {
std::shared_ptr<A> aptr;
~B() { cout << "B delete" << endl; }
};
int main() {
auto aaptr = std::make_shared<A>();
auto bbptr = std::make_shared<B>();
aaptr->bptr = bbptr;
bbptr->aptr = aaptr;
return 0;
}
上面代码,产生了循环引用,导致aptr
和bptr
的引用计数为2,离开作用域后aptr
和bptr
的引用计数-1,但是永远不会为0
,导致指针永远不会析构,产生了内存泄漏,如何解决这种问题呢,答案是使用weak_ptr
。
weak_ptr
weak_ptr
是用来监视shared_ptr
的生命周期,它不管理shared_ptr
内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr
中管理的资源是否存在,可以用来返回this
指针和解决循环引用问题。
- 作用1:返回
this
指针,上面介绍的shared_from_this()
其实就是通过weak_ptr
返回的this
指针。 - 作用2:解决循环引用问题。
struct A;
struct B;
struct A {
std::shared_ptr<B> bptr;
~A() { cout << "A delete" << endl; }
void Print() { cout << "A" << endl; }
};
struct B {
std::weak_ptr<A> aptr; // 这里改成weak_ptr
~B() { cout << "B delete" << endl; }
void PrintA() {
if (!aptr.expired()) { // 监视shared_ptr的生命周期
auto ptr = aptr.lock();
ptr->Print();
}
}
};
int main() {
auto aaptr = std::make_shared<A>();
auto bbptr = std::make_shared<B>();
aaptr->bptr = bbptr;
bbptr->aptr = aaptr;
bbptr->PrintA();
return 0;
}
输出:
A
A delete
B delete
unique_ptr
std::unique_ptr
是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr
的拷贝和赋值。使用方法和shared_ptr
类似,区别是不可以拷贝:
using namespace std;
struct A {
~A() { cout << "A delete" << endl; }
void Print() { cout << "A" << endl; }
};
int main() {
auto ptr = std::unique_ptr<A>(new A);
auto tptr = std::make_unique<A>(); // error, c++11还不行,需要c++14
std::unique_ptr<A> tem = ptr; // error, unique_ptr不允许移动
ptr->Print();
return 0;
}
unique_ptr
也可以像shared_ptr
一样自定义删除器,使用方法和shared_ptr
相同。
内存泄漏
出现内存泄露,一般由以下几个原因导致的:
- 忘记释放内存:动态分配的内存未被释放。
- 异常导致未释放内存:在释放内存之前发生异常。
- 循环引用:
std::shared_ptr
之间的循环引用导致引用计数无法归零。
如何避免内存泄露呢?
- 使用智能指针:优先使用
std::unique_ptr
和std::shared_ptr
管理内存。 - 避免手动管理内存:尽量减少使用
new
和delete
。 - 使用RAII(资源获取即初始化):将资源管理与对象的生命周期绑定。
代码示例:
#include <iostream>
#include <memory>
class Resource {
public:
Resource() {
std::cout << "Resource acquired." << std::endl;
}
~Resource() {
std::cout << "Resource released." << std::endl;
}
void use() {
std::cout << "Using resource." << std::endl;
}
};
class ResourceManager {
private:
std::unique_ptr<Resource> resource;
public:
ResourceManager() : resource(new Resource()) {}
void useResource() {
resource->use();
}
};
int main() {
ResourceManager manager;
manager.useResource();
// ResourceManager离开作用域时,自动释放Resource
return 0;
}
练习
- 使用
std::unique_ptr
实现一个简单的文件管理器类,确保文件句柄在对象销毁时自动关闭。 - 实现一个简单的资源池类,使用
std::shared_ptr
管理资源,并确保资源在不再使用时自动释放。 - 使用
std::weak_ptr
解决std::shared_ptr
的循环引用问题,并测试内存释放情况。
