在研究exception_ptr
的作用时,C ++ 11标准说(18.8.5 / 7):
对引用同一异常对象的exception_ptr对象使用rethrow_exception不应引入数据争用。 [注意:如果rethrow_exception重新抛出相同的异常对象(而不是副本),对该重新抛出的异常对象的并发访问可能会引入数据争用......
我找不到这种奇怪的情况"注意"适用,因为所描述的rethrow_exception
的效果是"抛出:p所指的异常对象"但是15.1 / 3,描述了投掷过程的一般异常,即抛出异常复制 - 初始化一个临时对象,称为异常对象。"
奇怪的说明意味着rethrow_exception会跳过此复制初始化。但这真的有可能吗?
答案 0 :(得分:3)
当您说throw x;
时,异常对象与x
具有相同的类型,但它是副本。
当你说std::rethrow_exception(p);
时,异常对象是指针引用的实际对象,不再进行复制。
因此,有几个线程同时重新抛出相同的异常指针(你可以复制它!),然后它们都引用了相同的对象。
答案 1 :(得分:3)
是的,它似乎是标准的缺陷。对于重新抛出的throw-expression,即没有操作数的throw;
,15.1p8说:
没有操作数的 throw-expression 重新抛出当前处理的异常。使用现有异常对象重新激活该异常;没有创建新的异常对象。 [...]
那是:
#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';
}
}
我在打印1 1
时尝试过的每个实现;如果0 0
份,则允许current_exception
; 0 1
显然是不可能的,而当前状态的标准似乎需要1 0
。使用类似于15.1p8的语言澄清18.8.5p10的修复,允许或强制rethrow_exception
不复制exception_ptr
指向的异常对象。
标准中的大多数投掷:规范只是命名一种类型(投掷:bad_alloc
)或使用不定冠词(抛出:异常类型...... );使用定冠词的唯一其他例外规范是future::get
和shared_future::get
,所以任何解决方案都应该解决这些问题。
答案 2 :(得分:2)
是的,这是可能的。异常处理机制已经有一个最初抛出的对象的副本,在私有内存中被squirleled。通常,exception_ptr
实现为智能指针,用于管理该副本的引用计数。
对于一般要求,如果特定要求与一般要求冲突,则具体要求获胜。