奇怪的行为,MSVC 2015中从std :: async引发异常
我只是从Visual Studio 2013升级到2015,我遇到了一些问题,这些问题曾经在2013年开始运作,但在2015年不会。
例如,这是一个让我难倒的人。 我从原始代码中创建了一个测试用例。
基本上,代码通过std :: async()在一个线程中运行一些操作。 在线程内,可能会抛出异常(A),它应放置在未来由std :: async()返回的对象中。 奇怪的是,在(B)中,Ex的析构函数被调用,但是对象仍然被抛出。 在try-block中,当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()
,该问题可以在其他情况下进行复制,而std::current_exception()
则涉及VC 14标准库实现中的副本(标准允许)。
问题是在crt/src/stl/excptptr.cpp
实现__ExceptionPtr::_CallCopyCtor
crt/src/stl/excptptr.cpp
。 它基本上做了一个测试,就像“有没有复制构造函数?不,好,我们可以做一个memcpy
!”。
另一个问题是测试忽略访问说明符。 如果你提供了一个拷贝构造函数,但是它是private
,它将被调用。
测试是在运行时完成的,所以没有机会出现编译时错误,而且不幸的是,这是实现它的唯一方法:没有一般的编译时测试会告诉什么类型的异常对象std::exception_ptr
将指向所有情况。
核心问题1863提出的决议旨在通过要求避免这个问题
选择用于复制初始化的构造函数,将抛出的对象视为左值应该不被删除并可访问。 [...]
问题处于Ready
状态,这是采用的一步。
所以,虽然这肯定是MSVC的一个问题,但它也与标准中的一个公开问题有关。 目前来看,为异常对象提供拷贝构造函数看起来是个不错的主意,即使标准不需要它(还)。
更新:针对问题1863的决议已被纳入N4567工作草案,因此,如果没有合适的构造函数,则实现该更改的编译器必须拒绝该代码。
似乎MSVC 2015仍然调用复制构造函数,即使它被标记为删除。 为了解决这个问题,我定义了复制构造函数。
打印输出的问题是因为拷贝构造函数中没有打印。 我添加了一些,并且构造函数/析构函数的数量相匹配。
不过,如果MSVC 2015被标记为删除,则不应该调用复制构造函数。 如果必须调用它,那么它应该发出一个错误。
链接地址: http://www.djcxy.com/p/30731.html上一篇: Weird behavior with exceptions thrown from std::async, in MSVC 2015