问题标签 [exception-safety]

For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.

0 投票
2 回答
1117 浏览

c++ - std::unordered_map::merge() 的安全性

在编写一些针对 C++17 的代码时,我遇到了一个绊脚石,用于确定合并两个兼容的 std::unordered_maps 的操作的异常安全性。根据当前的工作草案第 26.2.7 节,表 91 部分内容涉及以下条件a.merge( a2 )

要求: a.get_allocator() == a2.get_allocator()

尝试使用 的散列函数和键相等谓词提取每个元素a2并将其插入。在具有唯一键的容器中,如果某个元素的键与来自 的元素的键等效,则不会从 中提取该元素。aaaa2a2

后置条件:指向被转移元素的指针和引用a2指代那些相同的元素,但作为a. 引用被转移元素的迭代器和所有引用的迭代器a都将失效,但剩余元素的迭代器a2将保持有效。

抛出:除非散列函数或键相等谓词抛出,否则什么都没有。

值得注意的是,这些条件强烈地让人想起那些在 §26.2.6 表 90 中给出的普通关联容器 (std::map) 的要求a.merge( a2 )

要求: a.get_allocator() == a2.get_allocator()

尝试使用 的比较对象提取其中的每个元素a2并将其插入。在具有唯一键的容器中,如果某个元素的键与来自 的元素的键等效,则不会从 中提取该元素。aaaa2a2

后置条件:指向被转移元素的指针和引用a2指代那些相同的元素,但作为a. 引用被转移元素的迭代器将继续引用它们的元素,但它们现在表现为迭代器 into a,而不是 into a2

抛出:除非比较对象抛出,否则什么都没有。

我需要合并两个具有相同数量元素的 std::unordered_maps,我可以确保这在两个容器中都是唯一的,这意味着包含合并结果的地图的元素数量将是以前的两倍,并且容器合并- from 将是空的。多亏了 C++17,这应该是完全安全的,对吧?

就性能而言,这是一个巨大的胜利……除了,我有这个挥之不去的疑问。有趣的是,后置条件语句没有说明合并映射中的前一个最大负载因子是否会得到尊重,虽然这似乎是一个安全的隐含假设,但它似乎与关于 unordered_map 异常安全性的语句发生了天真的冲突。如果您使用哈希表设计,其中存储桶是连续分配的缓冲区,那么保持负载因子似乎意味着重新散列,这似乎意味着重新分配存储桶缓冲区。

这似乎已经是一种极端的凝视练习,并且有充分的理由不理会它:可以想象,可以将更复杂的哈希表制作为完全基于节点的结构,类似于通常支撑 std 的红黑树::map,在这种情况下,规范似乎是合理的,因为重新散列并不意味着分配。

也许对我有利的是,我屈服于怀疑并深入研究了 gcc-7.1 的合并实现。这非常复杂,但总结一下我的发现,我发现存储桶确实是连续分配的缓冲区,并且重新散列确实意味着重新分配。也许,我想,我错过了一些更深层次的魔法(我盯着源代码看了将近一整天,仍然觉得我对它的理解很差)它只是在合并期间禁用了重新散列,这意味着所有指定的条件会得到支持,但在适当大的合并后你可能会得到一个令人讨厌的性能回归,因为你的地图可能会超载。

我进行了反映我的用例的实际评估(如果可能的话,我会提出,对不起),而不是仅仅质疑我对 libstdc++ 的解释:

果然,在GCC-7.1下编译这段代码后,我得到:

然而,取消注释第 95 行 ( m1.reserve( m1.size() + m2.size() );) 会产生预期的输出:

了解 C++17 仍然是一个尚未最终确定的标准草案,并且 gcc 的实现是实验性的,我想我的问题是:

  1. 我是否严重误解了标准中规定的合并操作的安全性?是否有使用std::unordered_map::merge()我错过的最佳实践?我是否应该暗中负责确保桶的分配?std::unordered_map::reserve()当 clang、MSVC 和 Intel 最终支持 C++17 时,使用实际上是可移植的吗?我的意思是,当无法进行无异常合并时,我的程序中止确实遵循列出的后置条件……</li>
  2. 这实际上是标准中的缺陷吗?如果文本被复制粘贴,无序关联容器和普通关联容器之间措辞的相似性可能会导致意外的保证。
  3. 这只是一个 gcc 错误吗?

值得注意的是,在撰写本文之前,我确实检查了 gcc 的错误跟踪器,似乎没有发现与我的描述相匹配的开放错误,此外,我检查了 C++ 标准缺陷报告,同样似乎是空的(诚然,做了一个该网站的文本搜索正在加重,我可能不够彻底)。后者并不令人惊讶,因为标准缺陷及其解决方法或后果通常会在 gcc 的源代码中注明,而我在检查期间没有发现这样的符号。我尝试在最近的 clang 签出(超过一周)中编译我的示例代码,但是编译器出现了段错误,所以我没有进一步检查,也没有咨询 libc++。

0 投票
2 回答
263 浏览

memory-management - 为什么发生恐慌时析构函数会运行?

如果一个 Rust 程序发生恐慌,并且假设没有恐慌捕捉器(有一段时间没有),那么不运行析构函数并让操作系统在进程之后清理肯定是安全和好的。为什么 Rust 会展开线程?

我能想到的唯一原因是当没有操作系统来回收内存时,但除了那个利基之外,它似乎没有必要。

0 投票
1 回答
321 浏览

c++ - 使用 c++ 和 glfw 的 raii 架构

我目前正在尝试使用glfw在 C++ 中编写一个小型引擎来创建窗口。我想充分利用 raii 来提出一个异常安全的架构,并使引擎几乎不可能以错误的方式使用。但我目前正在努力解决一些问题,我觉得我错过了一些重要的东西,所以我在这里问,提前谢谢大家!

假设我有一个raii名为的基本结构,它在和中glfw_context调用。它也接受一个并在调用之前设置它,但我会省略这些细节,因为我想专注于基本问题。我还创建了一个类,它需要 a在wheras 中采用相同类型的参数但带有 a is的重载。glfwInit()ctorglfwTerminate()dtorerror-callbackglfwInit()glfw_windowconst glfw_context&ctorctorglfw_context&&deleted

这背后的想法是,当上下文是时,rvalue它只会在ctor调用期间存在,但glfwTerminate()会在所有窗口被正确销毁之前被调用(这发生在dtor类中glfw_window)。我moves正确实现了,glfw_windowglfw_context不能复制或移动。但问题从这里开始:我无法阻止用户创建多个glfw_context实例。所以我static现在和一个成员一起去了,因为glfw没有公开像glfwIsInit()这样的东西,它在我的框架范围内解决了这个问题(只有最旧的实例会调用glfwTerminate()),但这仍然不能保护用户免于编写这样的代码:

在这种情况下,上下文仍然不会超过窗口。

有解决这个问题的好方法吗?我不想把它作为要求放在文档中(像“上下文必须比每个窗口都活得久”之类的东西似乎对我来说并不适用)。

我目前的方法是使用 astd::shared_pointer< glfw_context >作为glfw_window's的参数ctor并将其存储为成员。但是,这仍然不能解决我的问题,因为我仍然可以make_shared< glfw_context >()使用不同的上下文并将它们传递给不同的窗口。并且由于只有第一个分配的实例会调用glfwTerminate()我仍然可以引发上下文在所有窗口之前被破坏的情况。

那么解决这类问题的正确方法是什么?无论用户如何尝试(错误)使用它,我能否构建一个在这里正常工作的漂亮架构?我的其他一些想法包括一个private ctoringlfw_context和一个static工厂方法与 - 方法相结合,shared_pointer但这感觉很像一个singleton,我怀疑这是处理事情的最佳方式。

0 投票
2 回答
4005 浏览

c++ - C++17 中的 std::make_shared() 更改

cppref中,在 C++17 之前,以下内容一直有效:

诸如此类的代码如果在调用之后并引发异常,f(std::shared_ptr<int>(new int(42)), g())可能会导致内存泄漏,而这是安全的,因为两个函数调用永远不会交错。gnew int(42)f(std::make_shared<int>(42), g())

我想知道 C++17 中引入的哪个更改使这不再适用。

0 投票
3 回答
1986 浏览

c++ - 如何在函数退出时运行清理代码?

C++ 类提供 RAII 习惯用法。因此,您不必关心异常:

但是,如果您(出于某些原因)使用某些纯 C API,则必须围绕它创建 C++ 包装器或使用 try/catch 块

即使您使用带有 RAII 习惯用法的 C++ 类,有时您也必须编写具有强大异常安全保证的代码:

但是这些结构非常笨重。

有没有办法强制在函数退出时运行一些清理代码?类似于atexit,但在函数范围内的东西......

有没有办法在不使用嵌套的 try/catch 块的情况下运行一些回滚代码?

我想要一些像这样工作的运算符或函数:

如果可以创建这样的功能,是否有任何理由避免使用它们?其他编程语言中是否有这样的结构?

0 投票
1 回答
227 浏览

c++ - 为什么 std::unique_ptr 没有明确要求 noexcept Deleter?

文档说 Deleter 应该是:

  • 不扔可构造
  • nothrow 可调用(因为它是从~unique_ptr() noexcept
  • nothrow destructible(出于上述原因)

我的问题是为什么uniqut_ptr定义为允许一个Deleter可能抛出的。例如,所有构造函数Deleter都允许以下内容:unique_ptr

现场演示

0 投票
1 回答
544 浏览

c++ - Implementing std::vector::push_back strong exception safety

I'm implementing my own vector based on post-2018 San Diego draft (N4791) and have some questions regarding implementing strong exception safety.

Here is some code:

I see 2 problems with this code. I've tried to follow the std::move_if_noexcept logic, but what if the element is nothrow move constructible but allocator_traits::construct throws exception in, say, some logging code inside custom allocator? Then my MoveAll call will throw and produce only basic guarantee. Is this a defect in the standard? Should there be more strict wording on Allocator::construct?

And another one in Rollback. It really produces strong guarantee only if the moved elements are nothrow move assignable. Otherwise, again, only basic guarantee. Is this how it is supposed to be?

0 投票
1 回答
192 浏览

c++ - 将对象移动到 std::vector 时的异常保证

如果在将对象移动到 std::vector 时内存分配失败,并且抛出 bad_alloc,std::vector 是否保证从对象移动的对象不变/仍然有效?

例如:

0 投票
1 回答
391 浏览

c++ - Memory leak during unordered_map::insert KeyEqual exception with GCC - breaking the strong exception safety guarantee?

I'm using GCC 7.3.1, but also tested on coliru, which I believe is version 9.2.0. Build with the following:

Here's rai.cpp:

Running it results in:

I don't see any memory leaks with Visual C++ (Microsoft (R) C/C++ Optimizing Compiler Version 19.24.28314 for x64).

Does this break the strong exception safety guarantee of unordered_map::insert (https://stackoverflow.com/a/11699271/1958315)? Is this a bug in the GCC STL?

0 投票
1 回答
71 浏览

c++ - Is it worth making my code less readable for providing exception safety in case of out of memory errors?

I have a game with non-copyable Items as they are supposed to be unique:

class Creature is capable of receiving an Item and adding it to their inventory:

This approach seems nice and clean but there's a small problem: if any part of my code can recover after std::bad_alloc and therefore catches one thrown by recieve_item()'s push_back(), the in-game logic is undefined: an Item was moved to a function which failed storing one so it is just lost. Moreover, the noexcept specifier has to be removed just for this possibility.

So, I can provide exception safety this way (please point it out if I'm wrong):

However, now we have new disadvantages:

  • the lack of std::move() in the caller's code obscures the fact of moving the item;
  • the "resize(+1)" part is just ugly and may be misunderstood during a code review.

The question is in the title. Is exception safety even for such a weird case considered a good design?