Boost C++ 库 | 智能指针(RAII、作用域指针、作用域数组)

2024-10-09 21:37:35 浏览数 (4)

点击上方"蓝字"关注我们

01、RAII

>>>智能指针的原理基于一个常见的习语叫做 RAII :资源申请即初始化。智能指针只是这个习语的其中一例——当然是相当重要的一例。智能指针确保在任何情况下,动态分配的内存都能得到正确释放,从而将开发人员从这项任务中解放了出来。这包括程序因为异常而中断,原本用于释放内存的代码被跳过的场景。用一个动态分配的对象的地址来初始化智能指针,在析构的时候释放内存,就确保了这一点。因为析构函数总是会被执行的,这样所包含的内存也将总是会被释放。 无论何时,一定得有第二条指令来释放之前另一条指令所分配的资源时,RAII 都是适用的。许多的 C 应用程序都需要动态管理内存,因而智能指针是一种很重要的 RAII 类型。不过 RAII 本身是适用于许多其它场景的。

代码语言:javascript复制
// mainwindow.h​#ifndef MAINWINDOW_H#define MAINWINDOW_H​#include <QMainWindow>#include <windows.h>#include <QDebug>​QT_BEGIN_NAMESPACEnamespace Ui {class MainWindow;}QT_END_NAMESPACE​class MainWindow : public QMainWindow{    Q_OBJECT​public:    MainWindow(HANDLE h) : m_handle(h){​    }​    ~MainWindow()    {        qDebug() << "~MainWindow()";        CloseHandle(m_handle);        delete ui;    }​    HANDLE handle() const {        return m_handle;    }​private:    Ui::MainWindow *ui;​    HANDLE m_handle;};#endif // MAINWINDOW_H​// main.cpp#include "mainwindow.h" // 包含自定义的主窗口头文件​#include <QApplication> // 包含Qt应用程序的核心功能​int main(int argc, char *argv[]){    QApplication a(argc, argv); // 创建一个 QApplication 对象,传入命令行参数    MainWindow w(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId())); // 创建 MainWindow 对象,打开当前进程以设置其信息    qDebug() << "GetCurrentProcessId = " << GetCurrentProcessId();    SetPriorityClass(w.handle(), HIGH_PRIORITY_CLASS); // 设置主窗口的进程优先级为高​    w.show(); // 显示主窗口    return a.exec(); // 执行 Qt 应用程序的事件循环}

OpenProcess 是 Windows API 中的一个函数,用于打开一个已存在的进程,并返回一个句柄,该句柄可用于后续的进程操作。其函数原型如下:HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);参数说明DWORD dwDesiredAccess:PROCESS_ALL_ACCESS: 请求所有权限。PROCESS_TERMINATE: 允许终止进程。PROCESS_QUERY_INFORMATION: 允许查询进程的信息。具体可查看 OpenProcess 函数文档。这个参数指定了请求的访问权限。在你的示例中,使用 PROCESS_SET_INFORMATION,这意味着请求设置进程信息的权限(例如,改变进程的优先级)。常用的访问权限常量包括:BOOL bInheritHandle:指定是否可以将返回的句柄继承到子进程。如果设置为 TRUE,则句柄可以被子进程继承;如果设置为 FALSE,则不能被继承。在你的代码中,这里传递的是 FALSE,表示不允许继承。DWORD dwProcessId:要打开的进程的标识符。在你的示例中,使用 GetCurrentProcessId() 函数获取当前进程的 ID,这样就是打开当前运行的进程。

  1. DWORD dwDesiredAccess:
    • PROCESS_ALL_ACCESS: 请求所有权限。
    • PROCESS_TERMINATE: 允许终止进程。
    • PROCESS_QUERY_INFORMATION: 允许查询进程的信息。
    • 具体可查看 OpenProcess 函数文档。
    • 这个参数指定了请求的访问权限。在你的示例中,使用 PROCESS_SET_INFORMATION,这意味着请求设置进程信息的权限(例如,改变进程的优先级)。
    • 常用的访问权限常量包括:
  2. BOOL bInheritHandle:
    • 指定是否可以将返回的句柄继承到子进程。如果设置为 TRUE,则句柄可以被子进程继承;如果设置为 FALSE,则不能被继承。在你的代码中,这里传递的是 FALSE,表示不允许继承。
  3. DWORD dwProcessId:
    • 要打开的进程的标识符。在你的示例中,使用 GetCurrentProcessId() 函数获取当前进程的 ID,这样就是打开当前运行的进程。

上面的例子中定义了一个名为 windows_handle 的类,它的析构函数调用了 CloseHandle() 函数。这是一个 Windows API 函数,因而这个程序只能在 Windows 上运行。在 Windows 上,许多资源在使用之前都要求打开。这暗示着一旦资源不再使用之后就应该关闭。 windows_handle 类的机制能确保这一点。windows_handle 类的实例以一个句柄来初始化。Windows 使用句柄来唯一的标识资源。比如说,OpenProcess() 函数返回一个 HANDLE 类型的句柄,通过该句柄可以访问当前系统中的进程。在示例代码中,访问的是进程自己——换句话说就是应用程序本身。我们通过这个返回的句柄提升了进程的优先级,这样它就能从调度器那里获得更多的 CPU 时间。这里只是用于演示目的,并没什么实际的效应。重要的一点是:通过 OpenProcess() 打开的资源不需要显示的调用 CloseHandle() 来关闭。当然,应用程序终止时资源也会随之关闭。然而,在更加复杂的应用程序里, windows_handle 类确保当一个资源不再使用时就能正确的关闭。某个资源一旦离开了它的作用域——上例中 h 的作用域在 main() 函数的末尾——它的析构函数会被自动的调用,相应的资源也就释放掉了。

02、作用域指针

>>>std::scoped_ptr 是 C 17 之前的一种智能指针,用于在栈上管理动态分配的对象的生命周期。它的特点是:拥有唯一性和局部性的资源管理。在现代 C 中,std::scoped_ptr 也被弃用了,取而代之的是 std::unique_ptr。 一个作用域指针独占一个动态分配的对象。对应的类名为 boost::scoped_ptr,它的定义在 boost/scoped_ptr.hpp 中。不像 std::auto_ptr,一个作用域指针不能传递它所包含的对象的所有权到另一个作用域指针。一旦用一个地址来初始化,这个动态分配的对象将在析构阶段释放。 因为一个作用域指针只是简单保存和独占一个内存地址,所以 boost::scoped_ptr 的实现就要比 std::auto_ptr 简单。在不需要所有权传递的时候应该优先使用 boost::scoped_ptr 。在这些情况下,比起 std::auto_ptr 它是一个更好的选择,因为可以避免不经意间的所有权传递。

代码语言:javascript复制
#include "boost/version.hpp"#include "boost/config.hpp"#include "boost/scoped_ptr.hpp" // 作用域指针#include "boost/scoped_array.hpp"#include "boost/shared_ptr.hpp"#include <vector>​#include <boost/date_time/gregorian/gregorian.hpp>​​#include <iostream>​​void task() {    std::cout << "do" << std::endl;}​using namespace  std;​int main(){    boost::scoped_ptr<int>i(new int);    *i = 1;    std::cout << "i=" << *i << std::endl;    *i.get() = 2;    std::cout << "i=" << *i << std::endl;    i.reset(new int);    std::cout << "i=" << *i << std::endl;​    return 0;}

一经初始化,智能指针 boost::scoped_ptr 所包含的对象,可以通过类似于普通指针的接口来访问。这是因为重载了相关的操作符 operator*(),operator->() 和 operator bool() 。此外,还有 get() 和 reset() 方法。前者返回所含对象的地址,后者用一个新的对象来重新初始化智能指针。在这种情况下,新创建的对象赋值之前会先自动释放所包含的对象。boost::scoped_ptr 的析构函数中使用 delete 操作符来释放所包含的对象。这对 boost::scoped_ptr 所包含的类型加上了一条重要的限制。 boost::scoped_ptr 不能用动态分配的数组来做初始化,因为这需要调用 delete[] 来释放。在这种情况下,可以使用下面将要介绍的 boost:scoped_array 类。在 C 11 之前,如果你在项目中使用 std::scoped_ptr,通常是在以下情况下:独占所有权:std::scoped_ptr 确保其持有的对象的唯一所有权。这意味着同一时间只能有一个 scoped_ptr 指向该对象,适合管理不需要共享的对象。自动内存管理:当 scoped_ptr 超出作用域时,它会自动调用析构函数,从而释放所管理对象的内存,避免了内存泄漏的问题。避免复杂的资源管理:使用 scoped_ptr 可以简化内存管理,因为不需要手动释放内存,从而降低了内存管理错误的风险

  1. 独占所有权std::scoped_ptr 确保其持有的对象的唯一所有权。这意味着同一时间只能有一个 scoped_ptr 指向该对象,适合管理不需要共享的对象。
  2. 自动内存管理:当 scoped_ptr 超出作用域时,它会自动调用析构函数,从而释放所管理对象的内存,避免了内存泄漏的问题。
  3. 避免复杂的资源管理:使用 scoped_ptr 可以简化内存管理,因为不需要手动释放内存,从而降低了内存管理错误的风险

03、作用域数组

>>>作用域数组的使用方式与作用域指针相似。关键不同在于,作用域数组的析构函数使用 delete[] 操作符来释放所包含的对象。因为该操作符只能用于数组对象,所以作用域数组必须通过动态分配的数组来初始化。 对应的作用域数组类名为 boost::scoped_array,它的定义在 boost/scoped_array.hpp 里。

代码语言:javascript复制
#include "boost/version.hpp"#include "boost/config.hpp"#include "boost/scoped_ptr.hpp" // 作用域指针#include "boost/scoped_array.hpp"#include "boost/shared_ptr.hpp"#include <vector>​#include <boost/date_time/gregorian/gregorian.hpp>​​#include <iostream>​​void task() {    std::cout << "do" << std::endl;}​using namespace  std;​int main(){    boost::scoped_array<int>i(new int[2]);    *i.get() = 1;    // 遍历数组元素并输出它们    for (int j = 0; j < 3;   j) {        std::cout << "Element1 " << j << ": " << i[j] << std::endl;    }    std::cout << std::endl;    i[1] = 2;    // 遍历数组元素并输出它们    for (int j = 0; j < 3;   j) {        std::cout << "Element2 " << j << ": " << i[j] << std::endl;    }    i.reset(new int[3]);    std::cout << std::endl;    // 遍历数组元素并输出它们    for (int j = 0; j < 3;   j) {        std::cout << "Element3 " << j << ": " << i[j] << std::endl;    }​​    return 0;}

boost:scoped_array 类重载了操作符 operator[]() 和 operator bool()。可以通过 operator[]() 操作符访问数组中特定的元素,于是 boost::scoped_array 类型对象的行为就酷似它所含的数组。正如 boost::scoped_ptr 那样, boost:scoped_array 也提供了 get() 和 reset() 方法,用来返回和重新初始化所含对象的地址。boost::scoped_array 提供了更简单的数组类型资源管理,但在 C 11 及其以后的版本中,推荐使用 std::unique_ptr 来替代它。使用 boost::scoped_array 的场景管理动态数组:在需要管理动态分配的数组时,可以使用 boost::scoped_array,它会在超出作用域时自动释放内存,避免内存泄漏。独占所有权:boost::scoped_array 唯一拥有其指向的动态数组,确保同一时间只有一个 scoped_array 可以管理该数组,适用于不需要共享所有权的情况。避免手动内存管理:使用 boost::scoped_array 可以简化内存管理,因为不需要显式地调用 delete[] 来释放数组,从而减少了内存管理错误的风险。

  1. 管理动态数组:在需要管理动态分配的数组时,可以使用 boost::scoped_array,它会在超出作用域时自动释放内存,避免内存泄漏。
  2. 独占所有权boost::scoped_array 唯一拥有其指向的动态数组,确保同一时间只有一个 scoped_array 可以管理该数组,适用于不需要共享所有权的情况。
  3. 避免手动内存管理:使用 boost::scoped_array 可以简化内存管理,因为不需要显式地调用 delete[] 来释放数组,从而减少了内存管理错误的风险。

>>>下节继续补充,保住头发。

0 人点赞