通过构造函数和析构函数实现 RAII 是否被认为是糟糕的"现代C++"?

Is implementing RAII via constructors and destructors considered bad 'Modern C++'?

本文关键字:现代 C++ 认为是 构造函数 析构函数 实现 是否 RAII      更新时间:2023-10-16

随着c++中智能指针的出现,通过构造函数和析构函数手动实现RAII是否被认为是糟糕的"现代c++"实践?或者是否存在与此相关的应用?

内存,通过分配,并不是唯一一种可以获取的资源,所以指针不是RAII的唯一一种野兽。

考虑一个作用域锁:

template <class Lockable>
class lock_guard {
    Lockable& lck;
public:
    lock_guard(Lockable& lck)
        : lck(lck)
    {
        lck.lock();
    }
    ~lock_guard()
    {
        lck.unlock()
    }
};

没有指针。仍然RAII。仍然闪亮,现代,超级有用。

这个问题基于危险的观点,可能会被关闭。然而,我将尝试提供一些事实和信息,以决定何时手动RAII可能是合适的。

当我们可以避免- manual - RAII

如果类中使用的堆内存都是通过智能指针或某种容器(如vector)分配的,则可以避免显式地创建析构函数,也可以避免手动定义繁琐的方法,如复制构造函数和赋值操作符。这是一件好事,当它是可能的,但并不总是合适的。现在,我们将简要讨论这种方法本身不够灵活的一些情况。

当我们无法轻易避免手动RAII

C代码的包装

C代码通常使用库提供的函数分配内存和其他资源,然后通常提供销毁这些资源的函数。在这种情况下,我们需要创建某种容器来在RAII中表示这些资源。此容器不可避免地要进行人工销毁等。

对于一些包装器来说,通过创建一个带有自定义销毁函数的智能指针来返回一个适当的指针在技术上是可能的。然而,这并不比仅仅定义一个类更整洁。

非内存资源

并不是计算机所有的资源都是内存,也不是所有的资源都适合智能指针使用。我们可能需要文件、套接字、系统范围内的互斥锁和操作系统提供的各种其他资源对象。

并不是所有这些都可以很好地表示在一个智能指针中,即使他们被迫使用一个自定义销毁函数,它可能比制作一个合适的类来包装这些资源更难看。

相互破坏的资源

尽管像内存这样的资源通常可以按任何顺序分配和释放。有些资源则不然。例如,如果我们正在与一个硬件设备对话,我们可以将该设备表示为一个资源,然后将其作为单独的资源。

在这种情况下,不可能方便地使用隐式RAII,因为我们需要控制销毁顺序,而不想让API的用户不得不记住以正确的顺序销毁所有内容。相反,使用带有引用计数的类或其他内部跟踪相互链接的资源的方法会更简洁。

单一责任原则

通常在上面,以及其他需要手动实现RAII的情况下。创建一个简单的对象来包装资源并对其提供低级操作是最简单的。

我们可以有更高层次的更复杂和功能的对象,而不必担心管理内存,本质上是将销毁和资源管理代码分离到自己的单元中。这消除了复杂析构函数等的许多痛苦。让一个对象使用10个资源,如果这些资源有很好的低级包装器来管理它们的生命周期和低级功能,就会容易得多。

如果可以使用标准的RAII包装器,就使用它们,不要重新发明轮子。不止是std::unique_ptr,还有其他的,比如std::lock_guard

在我的代码库中,我为绑定的opengl对象创建了一个RAII包装器。绑定也可以是一种资源!

简而言之,尽可能使用标准的RAII包装器。否则就执行你的。可以随意使用RAII