通过构造函数和析构函数实现 RAII 是否被认为是错误的 'Modern C++'?

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

随着 C++ 中智能指针的出现,通过构造函数和析构函数手动实现 RAII 是否被认为是不好的 'modern 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 包装器,就使用它们,不要重新发明轮子。不止 std::unique_ptr,还有其他的,比如 std::lock_guard.

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

简而言之,尽可能使用标准 RAII 包装器。否则实施你的。 RAII 想用多少就用多少。

这个问题基于危险的意见,可能会被关闭。但是,我会尝试提供一些事实和信息来决定何时手动 RAII 可能是合适的。

何时我们可以避免-Manual- RAII

的确,如果我们在 class 中使用了所有堆内存,要么通过智能指针分配,要么通过向量之类的容器分配,我们就可以避免显式创建析构函数,也可以避免手动定义繁琐的方法,例如作为复制构造函数和赋值运算符。如果可能但并不总是合适,这是一件好事。现在我们将简要讨论一些这种方法本身不够灵活的情况。

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

C 代码的包装器

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

通过尽可能创建具有自定义销毁函数的智能指针,对于某些包装器 return 一个适当的指针在技术上可能是可行的。然而,这并不比仅仅定义一个 class.

非内存资源

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

并非所有这些都可以用智能指针很好地表示,即使他们被迫使用具有自定义销毁功能的智能指针,也可能比制作适当的 class 来包装这些资源更难看。

相互依存破坏的资源

尽管内存等资源通常可以按任何顺序分配和释放。有些资源不是。例如,如果我们正在与硬件设备对话,我们可能会将设备表示为一种资源,然后将其功能表示为单独的资源。

在这种情况下,无法方便地使用隐式 RAII,因为我们需要控制销毁顺序并且不想让 API 的用户必须记住销毁一切都按正确的顺序进行。相反,使用带有引用计数的 class 或其他内部跟踪互连资源的方法会更整洁。

单一职责原则

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

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