我刚刚从 Visual Studio 2013 升级到 2015,我遇到了一堆问题,这些问题过去在 2013 年有效,但在 2015 年却没有。
例如,这是让我难过的一个。我用原始代码创建了一个测试用例。
基本上,代码通过 std::async() 在线程中运行一些操作。在线程内,可能会抛出异常 (A),这应该放在 std::async() 返回的 future 对象中。奇怪的是,在(B)中,调用了Ex的析构函数,但后面仍然抛出对象。在 try block 中,当 ex (D) 变量离开分数时,如果 'mInts' vector (X) 是成员,程序将崩溃。如果我将 'mInts' 注释掉,如下所示,我仍然会遇到奇怪的行为。例如,这是用下面的代码打印的:注意构造函数如何被调用一次,而析构函数被调用 4 次:
输出:
constructor
destructor
before exception
after exception
destructor
has exception
destructor
destructor
代码:
using FutureList = std::vector<std::future<void>>;
struct Ex {
Ex() {
std::cout << "constructor\n";
}
Ex(const Ex&) = delete;
Ex(Ex&&) {
std::cout << "move constructor";
}
~Ex() {
std::cout << "destructor\n";
}
void operator=(const Ex&) {
std::cout << "assign\n";
}
// std::vector<int> mInts; (X)
};
TEST(Explore, Test1) {
FutureList futures;
futures.push_back(
std::async(std::launch::async, []() {
throw Ex(); // (A)
}));
std::exception_ptr ex;
for (auto& i : futures) {
try {
i.get(); // (B)
std::cout << "Doesn't get here.\n";
}
catch (...) { // (C)
std::cout << "before exception\n";
ex = std::current_exception(); // (D)
std::cout << "after exception\n";
}
}
if (ex) {
std::cout << "has exception\n";
}
}
最佳答案
实际上,MSVC 不会在您的示例中调用复制构造函数。没有代码可以调用;该函数被删除。它做了更糟糕的事情:它将类视为可简单复制并在对象上执行 memcpy
。这就是当您拥有 std::vector<int>
类型的成员时发生崩溃的原因。
问题与 std::async
没有直接关系。这是由 std::exception_ptr
的实现引起的。一个std::future
的共享状态使用一个exception_ptr
来存储异常,所以在异常的上下文中使用一个future
会触发问题。仅使用 std::current_exception()
时可以重现该问题,它涉及 VC 14 标准库实现中的拷贝(标准允许这样做)。
问题在于 __ExceptionPtr::_CallCopyCtor
中 crt/src/stl/excptptr.cpp
的实现。它本质上做了一个测试,就像“是否有一个复制构造函数?没有?太好了,那么我们可以做一个 memcpy
!”。
另一个问题是测试忽略了访问说明符。如果您提供一个复制构造函数但将其设为 private
,它将被调用。
测试是在运行时完成的,所以不可能出现编译时错误,不幸的是,这是唯一的方法:没有通用的编译时测试可以告诉 std::exception_ptr
异常对象的类型在所有情况下都会指向。
Core issue 1863 的提议解决方案旨在通过要求避免此问题
[...] the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible. [...]
该问题处于 Ready
状态,距离采用仅一步之遥。
因此,虽然这绝对是 MSVC 的问题,但它也与标准中的一个未解决问题有关。目前,为异常对象提供复制构造函数似乎是个好主意,即使标准(目前)还不需要它。
更新:问题 1863 的解决方案已被采纳到 N4567 工作草案中,因此如果没有合适的构造函数可用,则要求实现更改的编译器拒绝代码。
关于c++ - MSVC 2015 中从 std::async 抛出异常的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31766940/