在MSVC 2015中,std::async抛出异常的奇怪行为
Weird behavior with exceptions thrown from std::async, in MSVC 2015
我刚刚从Visual Studio 2013升级到2015,我遇到了一堆问题,这些问题在2013年可以工作,但在2015年不行。
例如,这里有一个让我难住了。我从原始代码中创建了一个测试用例。
基本上,代码通过std::async()在线程中运行一些操作。在线程中,可能会抛出异常(A),异常应该放在std::async()返回的future对象中。奇怪的是,在(B)中,调用了Ex的析构函数,但之后仍然抛出对象。在try块中,当变量ex (D)离开分数时,如果'mInts'向量(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 << "constructorn";
}
Ex(const Ex&) = delete;
Ex(Ex&&) {
std::cout << "move constructor";
}
~Ex() {
std::cout << "destructorn";
}
void operator=(const Ex&) {
std::cout << "assignn";
}
// 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 exceptionn";
ex = std::current_exception(); // (D)
std::cout << "after exceptionn";
}
}
if (ex) {
std::cout << "has exceptionn";
}
}
实际上,在您的示例中,MSVC并不调用复制构造函数。没有代码可以调用;完成功能的删除。它做了一些更糟糕的事情:它将类视为平凡的可复制,并对对象执行memcpy
。这就是当您有std::vector<int>
类型的成员时崩溃的原因。
问题与std::async
没有直接关系。这是由std::exception_ptr
的实施引起的。std::future
的共享状态使用exception_ptr
来存储异常,因此在异常上下文中使用future
将触发问题。当只使用std::current_exception()
时,这个问题可能会重现,这涉及到vc14标准库实现的副本(标准允许)。
问题在于crt/src/stl/excptptr.cpp
中__ExceptionPtr::_CallCopyCtor
的实现。它本质上做了一个类似于"是否存在复制构造函数"的测试?没有?太好了,我们可以做memcpy
了!"
另一个问题是测试忽略了访问说明符。如果您提供了一个复制构造函数,但将其设置为private
,则它将被调用。
测试是在运行时完成的,所以没有机会出现编译时错误,不幸的是,这是唯一的方法:没有通用的编译时测试来告诉std::exception_ptr
在所有情况下将指向什么类型的异常对象。
Core issue 1863的提议决议旨在通过要求
[…考虑到被抛出的对象为左值,为复制初始化选择的构造函数必须是不可删除且可访问的。[…]
问题是在Ready
状态,离采用只有一步之遥。
所以,虽然这绝对是MSVC的一个问题,但它也与标准中的一个开放问题有关。现在,为异常对象提供复制构造函数看起来是个好主意,即使标准还没有要求。
更新:问题1863的决议已被采纳到N4567工作草案中,因此如果没有合适的构造函数可用,则需要实现该更改的编译器拒绝代码。
似乎MSVC 2015仍然调用复制构造函数,即使它被标记为已删除。为了解决这个问题,我定义了复制构造函数。
打印输出的问题是因为在复制构造函数中没有打印。我添加了一些,并且构造函数/析构函数计数匹配。
尽管如此,MSVC 2015不应该调用复制构造函数,如果它被标记为删除。如果必须调用,则应该发出错误。
- 从构造函数抛出异常时如何克服内存泄漏
- 函数如何通知用户它基于函数原型抛出异常?
- 在缺少函数重载时抛出异常,并带有 std::variant 而不是编译时错误
- VisualStudios 会抛出异常,而代码块不会 [C++]
- 如果不包含 pthread,为什么 GCC 的线程标准库实现会抛出异常?
- ZMQ::send() 抛出异常并终止 QNX 进程.为什么以及如何从中恢复?
- new(std::nothrow) int[n] 抛出异常
- 为什么g_object_set抛出异常(vcruntime140.dll)?
- 你能防止 std::regex 在无效表达式上抛出异常吗?
- C++子线程抛出异常
- 我们应该在抛出异常之前取消分配内存吗
- 有没有一种方法可以让OpenCLC++绑定为所有错误抛出异常
- 为什么访问模板化变体作为返回值抛出异常
- 如何实现,错误分配中止而不是抛出异常
- std::p riority_queue::p op什么时候会抛出异常
- 在0x5914F3BE抛出异常(基于.dll)
- std::vector 默认构造函数可以抛出异常吗?
- 挂钩创建文件抛出异常:读取访问冲突
- getaddrinfo 抛出异常
- 在MSVC 2015中,std::async抛出异常的奇怪行为