编
编程废柴
V1
2022/05/29阅读:19主题:默认主题
聊聊智能指针

聊聊智能指针系列(一)
聊聊RAII、智能指针以及C++17中一些相关的优化
第一章节 RAII以及最简单的智能指针实现
1.1 堆内存
在内存管理的语境下,堆指的是动态分配内存的区域。这里的内存在分配之后需要手动释放。在C++里分配内存使用new, 释放内存使用delete。 如果在使用完成之后没有完成释放,就会发生内存泄漏。如果程序持续地发生了很严重的内存泄漏,会导致在其他正常的业务逻辑中分配内存抛出异常,影响程序功能。一个简单的示例如下:直接在@Compiler Explorer玩耍
#include<iostream>
using namespace std;
class SomeClass{
public:
SomeClass() {
cout << "SomeClass()" << endl;
}
~SomeClass() {
cout << "~SomeClass()" << endl;
}
};
int main()
{
SomeClass *p = new SomeClass();
delete p;
auto p2 = new SomeClass();
// delete p2; <----memory leak here!
}
输出如下:
SomeClass()
~SomeClass()
SomeClass()
1.2 栈内存
栈内存是一段用于维持函数调用、本地变量存储的内存区,本地变量所使用的内存在“生命周期终结”时自动释放,针对有构造函数和析构函数的非POD(Plain Old Data)类型,编译器会在合适的地方插入析构函数。 示例如下:可以在@Compiler Explorer玩耍。
#include<iostream>
using namespace std;
// Type your code here, or load an example.
class SomeClass{
public:
SomeClass() {
cout << "SomeClass()" << endl;
}
~SomeClass() {
cout << "~SomeClass()" << endl;
}
};
int main()
{
{
SomeClass obj1;
}
SomeClass obj2;
}
输出如下:
SomeClass()
~SomeClass()
SomeClass()
~SomeClass()
更加重要的是,就算是发生了异常,编译器也会自动调用析构函数。具体示例如下:可以在@Compiler Explorer玩耍
#include<iostream>
using namespace std;
// Type your code here, or load an example.
class SomeClass{
public:
SomeClass() {
cout << "SomeClass()" << endl;
}
~SomeClass() {
cout << "~SomeClass()" << endl;
}
};
int main()
{
{
SomeClass obj1;
}
try{
SomeClass obj2;
cout << "befor throw" << endl;
throw("everything is ok?");
cout << "after throw" << endl;
} catch (const char *e) {
cout << "fine, thank you" << endl;
}
}
输出如下:
SomeClass()
~SomeClass()
SomeClass()
befor throw
~SomeClass()
fine, thank you
1.3 RAII
实际情况下,不得不使用堆上的内存,常见的几个原因:
-
对象很大 -
在编译器无法确认其类型,需要根据动态参数变化、生成 那么如何保证堆栈分配的内存能正确释放,哪怕是发生异常?正确是使用new和delete是不够的,因为可能在new和delete之间抛出了异常、根本无法执行到delete的位置。结合1.1和1.2的内容,一种很巧妙的做法是使用某个管理类,用该管理类创建本地对象,在管理类的构造函数中获取并保存指针、在管理类的析构中delete指针,这就是RAII(Resource Acquisition Is Initialization,资源获取之时就是管理对象初始化之时)。参照如下示例,可以在@Compiler Explorer玩耍
#include<iostream>
using namespace std;
// Type your code here, or load an example.
class SomeClass{
public:
SomeClass() {
cout << "SomeClass()" << endl;
}
~SomeClass() {
cout << "~SomeClass()" << endl;
}
};
class SomeManager{
public:
SomeManager(SomeClass *p): resource(p){
cout << "SomeManager(SomeClass *p)" << endl;
}
~SomeManager(){
cout << "~SomeManager(SomeClass *p)" << endl;
delete resource;
}
void print(){
cout << "rousource is " << resource << endl;
}
SomeClass *resource = nullptr;
};
int main()
{
{
SomeManager manager(new SomeClass());
}
try{
SomeManager manager(new SomeClass());
cout << "befor throw" << endl;
manager.print();
throw("everything is ok?");
cout << "after throw" << endl;
} catch (const char *e) {
cout << "fine, thank you" << endl;
}
}
输出如下
SomeClass()
SomeManager(SomeClass *p)
~SomeManager(SomeClass *p)
~SomeClass()
SomeClass()
SomeManager(SomeClass *p)
befor throw
rousource is 0x2052eb0
~SomeManager(SomeClass *p)
~SomeClass()
fine, thank you
事实上,所谓的资源不仅仅是内存,还有文件句柄、多线程编程中mutex等也都是资源,他们拥有同样的特征,就是使用完之后要保证释放,否则会影响正常功能。
1.4 最简单功能的智能指针实现
在1.3中的管理类SomeManager已经有智能指针最基础的用途了,但是明显太基础了。一个最明显的问题是,只能保存SomeClass这一种对象的指针。使用模板明显可以解决此问题。同时,一个基础的智能指针还应当:
-
提供方法获取其中裸指针 -
行为要像指针(可以解引用、也可以通过->访问成员、像指针一样用在bool表达式中)
基于以上的要求,可以重构下SomeManager生成一份简单的智能指针实现:可以在@Compiler Explorer玩耍
#include<iostream>
using namespace std;
class SomeClass{
public:
SomeClass() {
cout << "SomeClass()" << endl;
}
~SomeClass() {
cout << "~SomeClass()" << endl;
}
int data = 0;
};
template <typename T>
class SmartPointer{
public:
SmartPointer(T *p = nullptr):resource(p){}
~SmartPointer(){
delete resource;
}
T* get() {
return resource;
}
T& operator* () const {
return *resource;
}
T* operator-> () const{
return resource;
}
operator bool() const{
return resource;
}
T *resource = nullptr;
};
int main()
{
SmartPointer smart(new SomeClass());
smart->data = 42;
cout << "data is " << (*smart).data << endl;
if (smart) {
cout << "smart is not nullptr" << endl;
}
}
输出是:
SomeClass()
data is 42
smart is not nullptr
~SomeClass()
作者介绍
编
编程废柴
V1