9

在检查做什么exception_ptr时,C++11 标准说 (18.8.5/7):

在引用相同异常对象的 exception_ptr 对象上使用 rethrow_exception 不应引入数据竞争。[注意:如果 rethrow_exception 重新抛出相同的异常对象(而不是副本),则对该重新抛出的异常对象的并发访问可能会引入数据竞争......

我没有发现这个奇怪的“注意”适用的情况,因为描述的效果rethrow_exception是“抛出:p 引用的异常对象”但是 15.1/3,描述一般异常抛出过程要求“抛出异常副本- 初始化一个临时对象,称为异常对象。”

奇怪的注释意味着 rethrow_exception 跳过了这个复制初始化。但这真的可能吗?

4

3 回答 3

4

当您说throw x;时,异常对象具有与相同的类型x但是是副本。

当您说std::rethrow_exception(p);时,异常对象是指针引用的实际对象,不会再进行复制。

因此,多个线程同时重新抛出相同的异常指针(您可以复制它!),然后它们都具有对同一个对象的引用。

于 2013-02-08T15:12:12.663 回答
3

是的,这看起来像是标准的缺陷。对于重新抛出的 throw 表达式,即throw;没有操作数,15.1p8 说:

没有操作数的throw 表达式重新抛出当前处理的异常。使用现有的异常对象重新激活异常;没有创建新的异常对象。[...]

那是:

#include <exception>
#include <cassert>
int main() {
   std::exception *p = nullptr;
   try {
      try {
         throw std::exception();
      } catch(std::exception &ex) {
         p = &ex;
         throw;
      }
   } catch(std::exception &ex) {
      assert(p == &ex);
   }
}

如果实现current_exception复制当前处理的异常对象,没有办法判断是否rethrow_exception复制,但是如果它引用了异常对象,那么我们可以检查:

#include <exception>
#include <iostream>
int main() {
   std::exception_ptr p;
   try {
      try {
         throw std::exception();
      } catch(...) {
         p = std::current_exception();
         std::cout << (p == std::current_exception()) << ' ';
         std::rethrow_exception(p);
      }
   } catch(...) {
      std::cout << (p == std::current_exception()) << '\n';
   }
}

我在 prints 上尝试过的每个实现1 10 0允许current_exception复制;0 1显然是不可能的,而目前的标准似乎要求1 0. 修复将使用类似于 15.1p8 的语言对 18.8.5p10 进行澄清,允许或强制rethrow_exception不复制exception_ptr.

大多数Throws:标准中的规范只是命名一个类型(Throws:bad_alloc)或使用不定冠词(Throws: an exception of type ...);唯一使用定冠词的其他异常规范是future::getand shared_future::get,因此任何解决方案都应该解决这些问题。

于 2013-02-08T15:52:16.377 回答
2

是的,这是可能的。异常处理机制已经拥有一个最初被抛出的对象的副本,它被隐藏在一个私有内存存储中。通常,exception_ptr实现为管理该副本的引用计数的智能指针。

对于一般要求,如果具体要求与一般要求相冲突,则具体要求胜出。

于 2013-02-08T15:11:39.147 回答