1.什么是 RAII
1.概念
Resource Acquisition Is Initialization 资源获取即初始化。一般分为 3 步,当我们在main函数中声明一个局部对象的时候,会自动调用构造函数进行对象的初始化,当整个main函数执行完成后,自动调用析构函数来销毁对象,整个过程无需人工介入,由操作系统自动完成;
2.如何使用 raii
智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。
还有就是锁,有可能出现初始化锁但是没有释放锁的情况
2.智能指针
智能指针源码实现
导入头文件
#include <memory>
2.1 内存泄露
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定)内存块,使用完后必须显式释放的内存。应用程序般使用malloc,、realloc、 new等函数从堆中分配到块内存,
避免内存泄露的几种方式
- 计数法:使用new或者malloc时,让该数+1,delete或free时,该数-1,程序执行完打印这个计数,如果不为0则表示存在内存泄露
- 一定要将基类的析构函数声明为虚函数
~A() = default;
- 对象数组的释放一定要用delete []
- 有new就有delete,有malloc就有free,保证它们一定成对出现
检测工具
-
Linux下可以使用Valgrind工具
https://blog.csdn.net/qq_20553613/article/details/106503929
会显示有几个指针是 free 的有几个指针是 alloc 的,显示是在哪一行出错的,同样 Valgrind 还能检测其他的内存问题:
valgrind --tool=memcheck --leak-check=full ./a
使用空指针
使用野指针:指向了一个根本不存在或者已经被释放的指针
内存空间访问越界
内存空间未释放
内存空间重复释放
内存空间申请和释放不匹配
- Windows下可以使用CRT库
3.auto_ptr
定义:auto_ptr的实现原理其实就是RAII,在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载
抛弃原因:因为有拷贝构造和赋值的情况下原有的对象会被释放
https://blog.csdn.net/Dasis/article/details/121663794
(1)赋值
当我们对智能指针进行赋值时,如 ptest2 = ptest,ptest2会接管ptest原来的内存管理权,ptest会变为空指针,如果ptest2原来不为空,则它会释放原来的资源
(2)数组传递值,本质也是移动拷贝
void foo_ary()
{std::vector<std::auto_ptr<int>> Ary;std::auto_ptr<int> p(new int(3));//push_back时不允许如此构造对象Ary.push_back(p);printf("%d\r\n", *p);
}
4.shared_ptr
多个 shared_ptr 智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。
(1)创建
std::shared_ptr<int> p3(new int(10));
std::shared_ptr<int> p5 = std::make_shared<int>(10);
//调用拷贝构造函数
std::shared_ptr<int> p4(p3);//或者 std::shared_ptr<int> p4 = p3;
//调用移动构造函数
std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr<int> p5 = std::move(p4);
(2) 源码
//
// Created by xuboluo on 2022/9/5.
// shared_prt_demo#include <iostream>
template <typename T>
class my_shared{int count_;T* ptr_;public:// 创建构造函数// 1. 默认构造函数my_shared():count_(0),ptr_(nullptr){}// 2. 传入的是对象my_shared(T* ptr):ptr_(ptr),count_(1){}// 3.传入的是智能指针my_shared(const my_shared<T>& other):count_((other.count_+1)),ptr_(other.ptr_){}// 析构函数~my_shared(){count_--;if(ptr_&&count_==0){delete ptr_;}}// operator = 的方法my_shared* operator=(const my_shared<T>& other){if(this==&other) return this;other.count_++;// 将 prt2 指向的对象给 ptr1 ,并将自己原先的进行释放if(this->ptr_&&0==--this->count_){delete ptr_;}// 释放完之后再向 ptr 赋值this->ptr_ = other.ptr_;this->count_ = other.count_;return this;}// use_countint use_count(){return count_;}
};
int main(){int* p = new int(3);my_shared<int> ptr(new int(3)); // 传入一个对象的指针my_shared<int> ptr2(ptr); // 传入一个智能指针my_shared<int> ptr3 = ptr2;std::cout<<ptr3.use_count();
}
(3)Shared_ptr 的循环引用
如果相互引用 shared_ptr 会造成程序死锁,最终谁也不释放资源
Demo
5.weak_ptr
解决 shared_ptr 相互引⽤时,两个指针的引⽤计数永远不会下降为0,从⽽导致死锁问题。⽽ weak_ptr 是对对象的⼀种弱引⽤,可以绑定到 shared_ptr ,但不会增加对象的引⽤计数。
如何判断 weak_ptr 失效:1.expired():检查被引用的对象是否已删除。2.lock()会返回shared指针,判断该指针是否为空。3.use_count()也可以得到shared引用的个数,但速度较慢
(1)创建
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);
(2)如何使用
#include <iostream>
#include <memory>
using namespace std;int main()
{std::shared_ptr<int> sp1(new int(10));std::shared_ptr<int> sp2(sp1);std::weak_ptr<int> wp(sp2);//输出和 wp 同指向的 shared_ptr 类型指针的数量cout << wp.use_count() << endl; // 2//释放 sp2sp2.reset();cout << wp.use_count() << endl; // 1 //借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据cout << *(wp.lock()) << endl; // 10return 0;
}
6.unique_ptr
是作用域指针,当超出作用域时就会被销毁然后调用 delete。同⼀时间只能有⼀个智能指针可以指向该对象,每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收,而且 unique_ptr 的拷贝构造函数和 operator= 都被 delete 了
(1)创建
std::unique_ptr<int> p4(new int);
std::unique_ptr<int> p5(p4);//错误,堆内存不共享
std::unique_ptr<int> p5(std::move(p4));//正确,调用移动构造函数
(2)如何使用
#include <iostream>
#include <memory>
using namespace std;int main()
{std::unique_ptr<int> p5(new int);*p5 = 10;// p 接收 p5 释放的堆内存int * p = p5.release();cout << *p << endl; // 10//判断 p5 是否为空指针if (p5) {cout << "p5 is not nullptr" << endl;}else {cout << "p5 is nullptr" << endl; // null }std::unique_ptr<int> p6;//p6 获取 p 的所有权p6.reset(p);cout << *p6 << endl; // 10return 0;
}