C++ RAII初探+析构函数

开发 前端
AII并不是一个新鲜的特性,而是古早就有的一种范式。上面例子展示了对象创建的时候获取资源,对象销毁的时候释放资源的例子。

前言

早期编写的C++是有缺陷的,举些例子。比如裸指针满天飞,多线程的数据竞争,双重释放等等。但如今的C++正在努力改善这些缺陷,RAII范式的编程在C++比重逐步增加。RAII(Resource Acquisition Is Initialization)是C++之父Bjarne Stroustrup在设计C++的时候就引入了。即:资源获取即初始化。通俗点,在对象创建的时候获取资源,在对象销毁的时候释放资源。确保内存的安全性。指针shared_ptr就是其中的杰作,下面也会讲到。

本篇除了RAII之外,还会分析下其析构函数的关联。代码部分,经过C++20测试,均可跑通,可直接用。

RAII操作例子

一个非常简单的RAII操作,我们初始化对象的时候打开了文件资源。然后在离开对象的作用域的时候,会调用析构函数释放(关闭)文件资源,例子如下:

//filename:RAII.c
//compile:g++ -g -static -o RAII RAII.c


#include <iostream>
#include <memory>


class File {
public:
    File(const std::string& filename) {
        // 在构造函数中打开文件
        std::cout << "Opening file: " << filename << std::endl;
        file_ = fopen(filename.c_str(), "r");
        if (!file_) {
            throw std::runtime_error("Failed to open file");
        }
    }


    ~File() {
        // 在析构函数中关闭文件
        if (file_) {
            std::cout << "Closing file." << std::endl;
            fclose(file_);
        }
    }


private:
    FILE* file_;
};


int main() {
    try {
        // 创建 File 对象,RAII 确保文件在生命周期结束时自动关闭
        File f("example.txt");


        
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }


    
    return 0;
}

File对象的构造函数里面打开文件,上面代码运行的结果如下:

图片图片

在File对象f离开作用域也即是try块的结尾大括号处,会调用析构函数,关闭文件。

关于这点我们lldb验证下,且简略分析下其原理。在~File()析构函数下断,其堆栈是在RAII.c:36也即是try块大括号结尾的地方调用了析构函数。

如下:

(lldb) b ~File()
Breakpoint 2: where = RAII`File::~File() + 16 at RAII.c:17:13, address = 0x000000000040582c
(lldb) r&c
Process 4510 resuming
Opening file: example.txt
Process 4510 stopped
* thread #1, name = 'RAII', stop reason = breakpoint 2.1
    frame #0: 0x000000000040582c RAII`File::~File(this=0x00007fffffffe208) at RAII.c:17:13
   14
   15       ~File() {
   16           // 在析构函数中关闭文件
-> 17           if (file_) {
   18               std::cout << "Closing file." << std::endl;
   19               fclose(file_);
   20           }
(lldb) bt
* thread #1, name = 'RAII', stop reason = breakpoint 2.1
  * frame #0: 0x000000000040582c RAII`File::~File(this=0x00007fffffffe208) at RAII.c:17:13
    frame #1: 0x00000000004055ef RAII`main at RAII.c:36:5
    frame #2: 0x00000000004b7ec8 RAII`__libc_start_call_main + 104
    frame #3: 0x00000000004ba090 RAII`__libc_start_main + 624
    frame #4: 0x0000000000405475 RAII`_start + 37

当我们运行到try块收尾大括号处,看此时程序刚好调用了File::~File

(lldb) n
Opening file: example.txt
Process 4552 stopped
* thread #1, name = 'RAII', stop reason = step over
    frame #0: 0x00000000004055e3 RAII`main at RAII.c:33:5
   30           File f("example.txt");
   31
   32           // 文件操作...
-> 33       } catch (const std::exception& e) {
   34           std::cerr << e.what() << std::endl;
   35       }
   36
(lldb) di -s $pc
RAII`main:
->  0x4055e3 <+110>: lea    rax, [rbp - 0x58]
    0x4055e7 <+114>: mov    rdi, rax
    0x4055ea <+117>: call   0x40581c       ; File::~File at RAII.c:18:5
    0x4055ef <+122>: mov    eax, 0x0
    0x4055f4 <+127>: mov    rdx, qword ptr [rbp - 0x18]
    0x4055f8 <+131>: sub    rdx, qword ptr fs:[0x28]

也即是代码:

0x4055ea <+117>: call   0x40581c       ; File::~File at RAII.c:18:5

RAII风格指针

现代C++的几个指针

  • std::unique_ptr:独占所有权的智能指针。一个 unique_ptr 只能有一个指针指向资源,因此它不支持复制,只支持转移所有权。
  • std::shared_ptr:共享所有权的智能指针。多个 shared_ptr 可以共享对资源的所有权,只有最后一个指针被销毁时,资源才会被释放。
  • std::weak_ptr:一种不影响资源生命周期的智能指针,用来打破循环引用的问题

我们也来观察下RAII指针自动调用析构函数释放的例子

//filename:zhizhen.c
//compile:g++ -std=c++20 -g -static -o zhizhen zhizhen.c
#include <iostream>
#include <memory>


class Resource {
public:
    Resource(const std::string& name) : name_(name) {
        std::cout << name_ << " acquired!" << std::endl;
    }


    ~Resource() {
        std::cout << name_ << " released!" << std::endl;
    }


    void use() {
        std::cout << "Using " << name_ << std::endl;
    }


private:
    std::string name_;
};


void demonstrateWeakPtr() {
    // 创建 shared_ptr 管理 Resource 对象
    std::shared_ptr<Resource> sharedResource = std::make_shared<Resource>("Resource1");


    // 创建 weak_ptr 观察 shared_ptr
    std::weak_ptr<Resource> weakResource = sharedResource;


    // weak_ptr 不增加引用计数,它只是观察资源
    std::cout << "Weak pointer created, but it does not affect resource's reference count." << std::endl;


    // 使用 weak_ptr 的 lock 方法来获取 shared_ptr
    if (auto lockedResource = weakResource.lock()) {
        lockedResource->use();  // 使用资源
    } else {
        std::cout << "Failed to lock weak pointer, resource is not available." << std::endl;
    }


    // 当 shared_ptr 离开作用域时,资源会被释放
}


int main() {
    demonstrateWeakPtr();  // 资源由 shared_ptr 管理,weak_ptr 只是观察


    return 0;
}

它的结果如下,同样的析构函数在离开作用域释放

图片图片

结尾

RAII并不是一个新鲜的特性,而是古早就有的一种范式。上面例子展示了对象创建的时候获取资源,对象销毁的时候释放资源的例子。

我们只需要写好代码的规范,其它的编译器都给做了,比如析构函数的调用等。这种操作,有效的防范了部分内存泄露的可能性。

责任编辑:武晓燕 来源: 江湖评谈
相关推荐

2010-01-18 15:53:27

C++析构函数

2010-02-04 16:39:26

C++析构函数

2011-07-15 01:29:39

C++析构函数

2010-02-05 13:35:19

C++虚析构函数

2024-12-19 14:42:15

C++内存泄漏内存管理

2009-08-14 17:24:28

C#构造函数和析构函数

2009-09-03 13:14:55

C#构造函数C#析构函数

2009-08-19 09:57:01

C++ RAII

2011-06-09 15:04:22

RAII机制

2009-07-30 15:24:13

C#析构函数C#构造函数

2021-12-11 19:02:03

函数C++对象

2011-06-15 09:47:14

C++

2010-01-20 14:25:56

函数调用

2024-12-11 16:00:00

C++函数编译器

2010-01-25 10:10:42

C++函数参数

2009-09-02 10:49:46

C#调用析构方法

2010-07-20 09:52:27

Perl构造函数

2010-07-16 17:12:58

Perl析构函数

2009-12-04 17:16:41

PHP析构函数

2023-10-09 09:02:50

.Net析构函数分配
点赞
收藏

51CTO技术栈公众号