9

我最近在 c++ 中发现了关于 RAII 的内容,并且大多数 RAII 示例都在谈论异常安全。即使抛出异常,您如何始终释放资源。

我的问题是,如果您没有打开异常,RAII 是否值得。在我们公司,我们从事 arm 的嵌入式项目,默认情况下异常是关闭的,我们真的看不到对它们有任何需求。

感谢所有的答案!

4

4 回答 4

15

有例外的 RAII 基本上是一项要求。

RAII 无例外意味着您可以将资源的分配与代码结合起来以处置资源。

这使您可以拥有具有多个退出点的函数,简化析构函数的编写(在 RAII 繁重的环境中,析构函数通常是空的或默认的),可以简化对象分配和移动(同样,通常是空的或默认的,具有足够的 RAII 工作)。

嵌入式环境的一个经典示例是锁定和解锁某些互斥体。您要保证不会锁定互斥锁而忘记解锁它。要做到这一点,代码纪律意味着你必须有一个基本的函数退出点,并且有时你必须进行体操以确保这种情况发生。

使用 RAII,您只需创建一个拥有锁的 RAII 资源持有者。现在您可以随时返回,并且解锁资源的代码会自动注入返回站点。代码流被简化了,资源泄漏也少了很多。

RAII 也是令人惊叹的文档。带有 a 的结构或类Foo*可能意味着任何事情:您应该如何以及何时处理该资源尚不清楚。带有 a 的结构或类std::unique_ptr<Foo>显然拥有该指针。接受的函数std::unique_ptr<Foo>显然对传入的指针拥有所有权。返回的函数std::unique_ptr<Foo>显然赋予了您该指针的所有权。

于 2013-07-17T19:47:09.873 回答
7

C++ 中的 RAII 是一个更广泛的概念。这是一个习惯用法,可让您编写更安全的代码。RAII 的资源获取部分是您开始一些必须稍后结束的事情的地方,例如打开文件并稍后关闭它,分配一些内存并释放它,获取和释放锁。RAII 与以下重要概念有关:智能指针、线程安全(控制多线程程序中的互斥锁 - http://www.boost.org/doc/libs/1_49_0/doc/html/boost/interprocess/scoped_lock。 html这里是 RAII 的示例)、与文件的交互、对象所有权(当您使用智能指针时,就像unique_ptr您已经使用 RAII 一样)等等。

因此,无论异常如何,RAII 总是值得在好的 C++ 代码中使用。

于 2013-07-17T19:34:40.657 回答
2

我的问题是,如果您没有打开异常,RAII 是否值得。

当然值得!异常安全只是 RAII 的一个方面。另一个例子是避免泄漏动态创建的实例。

于 2013-07-17T19:30:43.490 回答
0

我实际上认为这取决于,也许我会在我的 C++ 同行中竞争最奇怪的答案。也就是说,如果您已经在使用 C++ 和 C++ 的丰富类型系统,我认为即使没有例外,在大多数情况下不使用 RAII 也是很疯狂的。

设计和使用接口时缺少 RAII 的麻烦

您通常不想编写 icky 函数来返回您在函数内部分配的资源,客户端必须手动和外部释放/关闭/销毁。同时,反转设计并让客户端分配资源并通过参数传递它们以供函数使用(例如填充客户端分配的缓冲区)可能很耗时,并且仍然使用客户端释放/关闭/销毁的责任(可以说是更清洁,因为客户端至少自己创建/打开/分配了资源)。仅仅设计一个返回可变长度字符串的函数,当它被调用时,其内容在函数内部确定是耗时的,而且无论是使用还是实现或两者都缺少 RAII,总是有点麻烦。

这些是在 C 中设计和使用接口的日常烦恼,如果您有资源在超出范围时自行清理,则不必在 C++ 中处理这些问题。只给一些构造函数和析构函数所花费的时间通常远远超过必须处理上述内容的额外麻烦。

缺乏 RAII 的危险

对于像作用域互斥锁这样的基本事物,类似的事情。它绝对可以保护您免受一些潜在的未来错误,以使互斥锁在超出范围时显式地自行解锁。在这种情况下,在第一次编写代码并对其进行测试时,在不涉及异常的情况下,个人避免错误可能很容易,但是某些同事可能会return在未来的关键时刻匆忙地将一些早期语句引入该函数并忘记unlock互斥锁如果这是明确要求的。无论多么可能或不可能,拥有这个作用域互斥体是件好事。

哑型系统的好处

但我是一个奇怪的类型,喜欢 C 和 C++ 并在两者之间来回反弹,我发现在 C 中更容易做一件事,那就是实现几乎可以处理任何数据的低级数据结构类型。这是因为在 C 中,您没有丰富的类型系统,其中的对象可以具有 vtables、dtors 和 ctors,并且您没有异常。您通常可以将数据类型视为此处和此处的位和字节,memcpy此处和memmove那里malloc的任何内容的内存realloc。我们可以在 C 中自信地做到这一点的原因是因为类型系统非常愚蠢并且缺乏所有这些特性。

当然,我用 C 设计的数据结构使用起来并不方便。它们缺乏类型安全性并且经常处理void*指针,它们必须被手动销毁等。它们非常便于实现并且使缓存友好,同时最小化堆分配,因为它们让我在我真正专注于内存布局和表示时可以只将数据类型视为要洗牌的位和字节。std::vector同时回到 C++ 中,在placement new通过std::allocator. 但是,std::vector与我在 C 中设计的任何数据结构相比,使用起来更方便、更安全。

因此,拥有一个缺少析构函数和构造函数等的类型系统有一种特殊的便利,如果它获得析构函数和构造函数以允许符合 RAII 的资源,C 不一定会因其擅长的方面而变得更好,因为突然之间各种现有的日常 C 函数memcpy将不再理智地使用。在这种类型系统下,它们突然成为最致命、最容易出错的函数,就像它们在 C++ 中一样。

因此,我实际上认为有一个论点是,在某些低级域中,RAII 实际上可能是一个障碍,尽管仅适用于这些非常低级的域。同时,无论有无例外,它对其他一切都非常有益。例外情况几乎将 RAII 变成了一项要求,但不包括这些非常低级的域,无论如何它们仍然非常有用。

可简单构造/可破坏的 UDT

有时我希望structC++ 中的关键字将结构简化为简单的可构造和可破坏的普通旧数据类型(memcpy例如,我们可以安全地绕过的东西的类型),编译器保护措施可以防止它存储任何数据成员吨。如果是这样,我会发现使用 C 的理由更少,因为那时我们可能会编写只能与 suchstructs和 not一起使用的泛型容器classes,而不依赖于类型特征来确定泛型类型是否可轻松构造/可破坏。因为对 C 的吸引力从来不在于缺少 RAII,而是能够轻松假设我们存储在数据结构中并每天使用的许多数据类型不需要它。在实现数据结构时很高兴知道你可以说,free用于元素的连续内存块,N无需遍历它们并调用析构函数。在 C++ 中,您经常不得不在更安全的方面犯错,并假设几乎所有东西都需要它(或将来会需要它),以至于memcpy强烈、强烈不鼓励任何人在任何地方使用它。

于 2018-01-17T03:44:45.367 回答