编程废柴

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