奇怪的行为,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

下一篇: Why should I use std::async?