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临时将来返回

下一篇: 奇怪的行为,MSVC 2015中从std :: async引发异常