C++ MSVC 2015中std::async引发异常的奇怪行为

C++ MSVC 2015中std::async引发异常的奇怪行为,c++,visual-studio-2015,C++,Visual Studio 2015,我刚从VisualStudio2013升级到2015年,我遇到了一系列的问题,这些问题过去在2013年可以解决,但在2015年却无法解决 例如,这里有一个让我感到困惑的问题。我用原始代码创建了一个测试用例 基本上,代码通过std::async()在线程中运行一些操作。在线程中,可能会抛出异常(A),异常应该放在std::async()返回的未来对象中。奇怪的是,在(B)中,调用了Ex的析构函数,但对象仍然是向后抛出的。在try块中,当ex(D)变量离开分数时,如果'mInts'向量(X)是成员,

我刚从VisualStudio2013升级到2015年,我遇到了一系列的问题,这些问题过去在2013年可以解决,但在2015年却无法解决

例如,这里有一个让我感到困惑的问题。我用原始代码创建了一个测试用例

基本上,代码通过std::async()在线程中运行一些操作。在线程中,可能会抛出异常(A),异常应该放在std::async()返回的未来对象中。奇怪的是,在(B)中,调用了Ex的析构函数,但对象仍然是向后抛出的。在try块中,当ex(D)变量离开分数时,如果'mInts'向量(X)是成员,程序将崩溃。如果我把“薄荷糖”注释掉,如下所示,我仍然会有奇怪的行为。例如,下面的代码就是这样打印的:注意构造函数是如何调用的,而析构函数调用了4次:

输出:

constructor    
destructor   
before exception   
after exception  
destructor   
has exception   
destructor   
destructor
代码:

使用FutureList=std::vector;
结构物{
Ex(){

std::cout看起来MSVC 2015仍然调用复制构造函数,即使它被标记为已删除。为了避免这个问题,我定义了复制构造函数

打印输出的问题是因为复制构造函数中没有打印。我添加了一些,并且构造函数/析构函数计数匹配


尽管如此,如果复制构造函数被标记为已删除,MSVC 2015不应该调用它。如果必须调用它,那么它应该发出一个错误。

实际上,在您的示例中,MSVC不调用复制构造函数。没有可调用的代码;函数被删除。它做了更糟糕的事情:它将类视为可复制的,并执行操作e> 对象上的memcpy
。当您有类型为
std::vector
的成员时,这就是崩溃的原因

该问题与
std::async
没有直接关系。它是由
std::exception_ptr
的实现引起的。
std::future
的共享状态使用
exception_ptr
存储异常,因此在异常上下文中使用
future
将触发该问题。该问题可以通过其他方式解决仅使用
std::current_exception()
时,可能会复制se,这涉及到VC14标准库实现中的副本(该标准允许)

问题在于
crt/src/stl/excptr.cpp中的
\uu ExceptionPtr::\u CallCopyCtor
的实现。它基本上执行一个测试,类似于“有副本构造函数吗?没有?太好了,我们可以做
memcpy

另一个问题是测试忽略了访问说明符。如果您提供了一个副本构造函数,但将其设置为私有的,则会调用它

测试是在运行时完成的,因此不可能出现编译时错误,不幸的是,这是唯一的方法:没有通用的编译时测试可以告诉您在所有情况下,
std::exception\u ptr
将指向什么类型的异常对象

拟议的解决方案旨在通过要求

[…]为副本初始化选择的构造函数(将抛出的对象视为左值)应不可删除且可访问。[…]

问题处于
Ready
状态,距离采用还有一步之遥

因此,虽然这肯定是MSVC的一个问题,但它也与标准中的一个开放问题有关。目前,为异常对象提供一个副本构造函数似乎是一个好主意,即使标准不需要它(目前)



更新:1863问题的解决方案已被采纳到N4567工作草案中,因此,如果没有合适的构造函数,则执行更改的编译器必须拒绝该代码。

真奇怪。我刚才提到了另一个问题,关于VC++6.0错误,它两次调用异常对象的析构函数。不管怎样,你明白了吗构造函数和析构函数调用之间存在相同的差异,现在你已经允许VC++对复制构造函数的错误调用了?@Cheers和hth。-阿尔夫:现在没有差异。打印输出的问题是因为复制构造函数中没有打印。我添加了一些,并且构造函数/析构函数计数匹配。如果你能你可以写下你的发现作为你问题的答案,并接受它。如果你想知道的话,这完全没关系。它帮助了别人,给了你一些急需的声望点。;-)请抽象掉这个
测试
宏,并添加一个
main
函数,这样我们就可以按原样重现你的问题。@bogdan-你继续,repo别这样。
using FutureList = std::vector<std::future<void>>;

struct Ex {
  Ex() {
    std::cout << "constructor\n";
  }

  Ex(const Ex&) = delete;
  Ex(Ex&&) {
    std::cout << "move constructor";
  }

 ~Ex() {
    std::cout << "destructor\n";
  }

  void operator=(const Ex&) {
    std::cout << "assign\n";
  }

// 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 exception\n";
        ex = std::current_exception();    // (D)
        std::cout << "after exception\n";
    }
  }

  if (ex) {
    std::cout << "has exception\n";
  }
}