通过构造函数和析构函数实现 RAII 是否被认为是糟糕的"现代C++"?
Is implementing RAII via constructors and destructors considered bad 'Modern C++'?
随着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
。
简而言之,尽可能使用标准的RAII包装器。否则就执行你的。可以随意使用RAII
- 带过滤器的现代迭代c++集合
- 现代CMake(3.8+)-FindCUDA已弃用,非.cu文件呢
- 拥有映射的现代方法,该映射可以指向或引用已在堆栈上分配的不同类型的数据
- 现代 C++ 中作为类成员的非拥有指针
- 使 std::vector 分配对齐内存的现代方法
- 具有字符串化的可变参数宏的现代/通用方法
- 将旧管道转换为现代 openGL 时出现问题
- 在现代OpenGL中,绘制GL_TRIANGLE奇怪的z轴行为的填充圆
- 现代OpenGL和GLEW Libraray的编译错误
- 如何将旧的 C 样式 #define 映射与现代C++进行调整?
- 以现代 CMake 方式控制包含的库中的构建选项(测试等)
- C++使用现代编译器编译的项目,但链接到过时的libstdc++
- 如何使用现代 CMake 安装捆绑的接口依赖项?
- 现代C++的libcurl和JSON问题
- 如何在现代C++中实现没有宏的系统特定功能
- 在现代C++中,侵入式容器是否仍然比非侵入式容器具有性能优势?
- 在现代 CMake 项目中存档静态依赖项
- 构造函数是否有一种现代C++方法来了解其'container'类?
- 在现代 CMake 中添加接口库作为系统
- 现代计算机上的双倍和浮点内存分配