内存泄漏的原因
智能指针自动销毁new出来的对象,智能指针可以自动完成垃圾清理这个工作
内存泄漏(memory leak)是指程序在动态分配内存后未能正确释放,导致这些内存无法被回收和重用。
1.忘记释放内存,动态分配了内存但是没有调用delete或free释放内存
2.丢失指针,动态分配内存的指针被覆盖或丢失,导致无法释放内存
int main(){
int *ptr = new int(5);
ptr = new int(6);
delete ptr;
return 0;
}
3.循环引用,发生在两个或多个对象相互引用对方,最终形成一个环,导致无法被正确释放
#include <iostream>
using namespace std;
#include <iostream>
class B;
class A {
public:
B* b;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
A* a;
~B() { std::cout << "B destroyed\n"; }
};
void createCycle() {
A* a = new A();
B* b = new B();
a->b = b;
b->a = a;
}
int main() {
createCycle();
return 0;
}
解决内存泄漏
1.正确使用内存的分配与回收
2.使用智能指针
std::unique_ptr:独占所有权,超出作用域时自动释放内存。std::shared_ptr:共享所有权,引用计数为零时自动释放内存。std::weak_ptr:与std::shared_ptr搭配使用,解决循环引用问题。
智能指针案例
下面的程序有内存泄漏
#include <iostream>
using namespace std;
int div(){
int a, b;
cin >> a >> b;
if (b == 0){
throw invalid_argument("除0错误");
}
return a / b;
}
void Func(){
int* p1 = new int[10];
int* p2 = new int[10];
cout << div(1,0) << endl;
delete[] p1;
delete[] p2;
}
int main(){
try{
Func();
}
catch (exception& e){
cout << e.what() << endl;
}
return 0;
}
如果div()函数抛出异常,代码将不会继续执行,导致 delete[] p1 和 delete[] p2不会被调用,从而产生内存泄漏。
为了避免内存泄漏,可以使用智能指针来管理内存。例如 std::unique_ptr
#include <memory>
void Func(){
std::unique_ptr<int[]> p1(new int[10]);
std::unique_ptr<int[]> p2(new int[10]);
cout << div() << endl;
}
无论是否发生异常,智能指针都会自动释放内存,确保没有内存泄漏
智能指针使用及原理
RAII思想
RAII是一种利用对象生命周期来控制程序资源(例如内存、文件句柄、网络连接、互斥量等)的简单技术.
获取到资源以后去初始化一个对象,将资源交给这个对象管理:资源获取即初始化
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。 借此,我们实际是把管理一份资源的责任托管给了一个对象,这种做法有两大好处
- 不需要显式的释放资源
- 采用这种方式,对象所需的资源在其生命周期内始终有效
使用RAII的思想设计一个智能指针SmartPtr
#include <iostream>
#include <memory>
using namespace std;
template <typename T>
class SmartPtr{
private:
T *_ptr;
public:
SmartPtr(T *ptr = nullptr):_ptr(ptr){}
~SmartPtr(){
cout<<"delete"<<_ptr<<endl;
if (_ptr){
delete _ptr;
_ptr = nullptr;
}
}
};
int div1(int a, int b){
if (b==0){
throw invalid_argument("div 0 error");
}
return a/b;
}
int main(){
SmartPtr<int> p1(new int);
SmartPtr<int> p2(new int);
try{
cout<<div1(1,0)<<endl;
}catch(exception& e){
cout<<e.what()<<endl;
}
return 0;
}
div 0 error
delete0x26124b0
delete0xfe3f80
在此基础上还需要加上指针解引用,通过->访问指向空间的内容,以及解决浅拷贝的问题。
C++函数库里的智能智能指针
unique_ptr
提供独占式所有权的内存管理,它不允许拷贝构造,但支持移动语义move
避免了多个指针指向同一块内存的情况,并在指针超出作用域时自动释放内存。
创建unique_ptr
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> ptr(new int(10));
std::cout << "value: " << *ptr << std::endl;
return 0;
}
使用make_unique
make_unique 是一种更安全的创建 unique_ptr 的方法,避免了使用 new 关键字
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> ptr = std::make_unique<int>(10);
std::cout << "Value: " << *ptr << std::endl;
return 0;
}
使用std::move转移所有权
unique_ptr 不允许复制,但可以通过 std::move 转移所有权
#include <iostream>
#include <memory>
int main() {
auto ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1);
if (!ptr1) {
std::cout << "ptr1 is null" << std::endl;
}
std::cout << "Value: " << *ptr2 << std::endl;
return 0;
}
管理动态数组
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int[]> arr(new int[5]);
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
unique_ptr的单一所有权
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::cout << "Value: " << *ptr1 << std::endl;
// std::unique_ptr<int> ptr2 = ptr1; // 错误,unique_ptr 不可复制
return 0;
}
shared_ptr
多个 shared_ptr 对象可以指向同一个对象,当最后一个 shared_ptr 释放时,该对象的内存才会被释放
创建和共享shared_ptr
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(30);
std::shared_ptr<int> ptr2 = ptr1;
std::cout << "Value from ptr1: " << *ptr1 << std::endl;
std::cout << "Value from ptr2: " << *ptr2 << std::endl;
std::cout << "Use count: " << ptr1.use_count() << std::endl;
return 0;
}
避免循环引用
shared_ptr 可能导致循环引用,从而造成内存泄漏。使用 std::weak_ptr 可以打破循环引用
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;
~Node() {
std::cout << "Node destroyed" << std::endl;
}
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1;
std::cout << "Use count of node1: " << node1.use_count() << std::endl; // 输出 1
std::cout << "Use count of node2: " << node2.use_count() << std::endl; // 输出 1
return 0;
}
weak_ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期。也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
解决方案就是,使用 std::weak_ptr 来代替其中任意一个方向上的 std::shared_ptr
它提供了一种不参与对象引用计数的弱引用,这意味着它不会影响所管理对象的生命周期。weak_ptr 只能从shared_ptr或另一个 weak_ptr 构造
也就是说,weak_ptr是为了弥补shared_ptr循环引用而生的,它没有RAII的特性,不直接管理资源,只是shared_ptr的跟班,这也是weak_ptr支持使用shared_ptr构造的原因。
在使用上,weak_ptr支持指针所有的操作。它不是一个功能型的智能指针,而是辅助型,它的使命是解决shared_ptr造成的循环引用问题。
与 shared_ptr 不同,weak_ptr 不能直接访问所指向的对象。要访问对象,需要先调用 lock() 方法将其转换为 shared_ptr。如果所指向的对象已经被销毁,则 lock() 方法返回空指针。 即便转换为shared_ptr,也不占用引用次数
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(100);
std::weak_ptr<int> weakPtr = sharedPtr;
if (auto lockedPtr = weakPtr.lock()) {
std::cout << "Value: " << *lockedPtr << std::endl; // 输出 100
} else {
std::cout << "Pointer is expired" << std::endl;
}
std::cout<< sharedPtr.use_count()<<std::endl; //1
sharedPtr.reset(); // 手动释放 shared_ptr 所管理的对象
std::cout<< sharedPtr.use_count()<<std::endl; //0
//检查 weakPtr 观察的对象是否已被销毁
if (weakPtr.expired()) {
std::cout << "Pointer is expired" << std::endl; // 输出 Pointer is expired
}
return 0;
}