Weird behavior with exceptions thrown from std::async, in MSVC 2015
I just upgrade from Visual Studio 2013 to 2015, and I'm running into a bunch of issues with things that used to work in 2013, but which do not in 2015.
For example, here's one that has me stumped. I created a test-case out of the original code.
Basically, the code runs some operations in a thread, via std::async(). Within the thread, exceptions might be thrown (A), which should be placed in the future object returned by std::async(). The weird thing is that in (B), the destructor of Ex is called, but the object is still thrown aftewards. In the try-block, when the ex (D) variable leaves the score, if 'mInts' vector (X) is a member, the program would crash. If I leave 'mInts' commented out, as below, I still get weird behavior. For example, this is what's printed with the code below: notice how the constructor is called one, but the destructor is called 4 times:
Output:
constructor
destructor
before exception
after exception
destructor
has exception
destructor
destructor
Code:
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";
}
}
Actually, MSVC doesn't call the copy constructor in your example. There's no code to call; the function is deleted. It does something worse: it treats the class as being trivially copyable and does a memcpy
on the object. That's the reason for the crash when you have a member of type std::vector<int>
.
The problem is not directly related to std::async
. It's caused by the implementation of std::exception_ptr
. The shared state of a std::future
uses an exception_ptr
to store an exception, so using a future
in the context of exceptions will trigger the problem. The issue can otherwise be reproduced when using only std::current_exception()
, which involves a copy in VC 14's standard library implementation (the Standard allows that).
The problem is with the implementation of __ExceptionPtr::_CallCopyCtor
in crt/src/stl/excptptr.cpp
. It essentially does a test that goes like "Is there a copy constructor? No? Great, we can do a memcpy
then!".
Another problem is that the test ignores access specifiers. If you provide a copy constructor but make it private
, it will be called.
The test is done at runtime, so no chance for a compile-time error, and, unfortunately, that's the only way to do it: there is no general compile-time test that will tell what type of exception object an std::exception_ptr
will point to in all cases.
The proposed resolution for Core issue 1863 aims to avoid this problem by requiring that
[...] the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible. [...]
The issue is in Ready
status, which is one step away from adoption.
So, while this is definitely a problem with MSVC, it's also related to an open issue in the Standard. For now, it looks like a good idea to provide a copy constructor for exception objects, even if the Standard doesn't require it (yet).
Update: The resolution for issue 1863 has been adopted into the N4567 working draft, so compilers implementing the change are required to reject the code if a suitable constructor is not available.
It seems that MSVC 2015 still calls the copy constructor, even though it's marked deleted. To get around this issue, I defined the copy constructor.
The issue with the printout was because there was no printing in the copy constructor. I added some, and the constructor/destructor count matched.
Still, MSVC 2015 shouldn't be calling the copy-constructor if it's marked deleted. If it must be called, then it should issue an error.
链接地址: http://www.djcxy.com/p/30732.html上一篇: std :: async临时将来返回