c++ - MSVC 2015 中从 std::async 抛出异常的奇怪行为

标签 c++ visual-studio-2015

我刚刚从 Visual Studio 2013 升级到 2015,我遇到了一堆问题,这些问题过去在 2013 年有效,但在 2015 年却没有。

例如,这是让我难过的一个。我用原始代码创建了一个测试用例。

基本上,代码通过 std::async() 在线程中运行一些操作。在线程内,可能会抛出异常 (A),这应该放在 std::async() 返回的 future 对象中。奇怪的是,在(B)中,调用了Ex的析构函数,但后面仍然抛出对象。在 try block 中,当 ex (D) 变量离开分数时,如果 'mInts' vector (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 << "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";
  }
}

最佳答案

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

问题与 std::async 没有直接关系。这是由 std::exception_ptr 的实现引起的。一个std::future的共享状态使用一个exception_ptr来存储异常,所以在异常的上下文中使用一个future会触发问题。仅使用 std::current_exception() 时可以重现该问题,它涉及 VC 14 标准库实现中的拷贝(标准允许这样做)。

问题在于 __ExceptionPtr::_CallCopyCtorcrt/src/stl/excptptr.cpp 的实现。它本质上做了一个测试,就像“是否有一个复制构造函数?没有?太好了,那么我们可以做一个 memcpy!”。

另一个问题是测试忽略了访问说明符。如果您提供一个复制构造函数但将其设为 private ,它将被调用。

测试是在运行时完成的,所以不可能出现编译时错误,不幸的是,这是唯一的方法:没有通用的编译时测试可以告诉 std::exception_ptr 异常对象的类型在所有情况下都会指向。

Core issue 1863 的提议解决方案旨在通过要求避免此问题

[...] the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible. [...]

该问题处于 Ready 状态,距离采用仅一步之遥。

因此,虽然这绝对是 MSVC 的问题,但它也与标准中的一个未解决问题有关。目前,为异常对象提供复制构造函数似乎是个好主意,即使标准(目前)还不需要它。


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

关于c++ - MSVC 2015 中从 std::async 抛出异常的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31766940/

相关文章:

c++ - 合并两个文本文件的库

c++ - pdCurses 在 Windows 中使用,与 3 个 gcc 宏混淆

c++:如何找到哪个标准库包含丢失的外部符号?

c++ - 为什么我在写 = 而不是 == 时没有收到警告?

c++ - 在 MSVC 编译器下使用最大成员初始化 union

android - Visual Studio 2015 Android 模拟器问题

c# - 监 window 口中的 $ReturnValue 在 VS2015 中不起作用

c++ - 迭代顺序集合的习惯用法

java - 什么是 Erlang 替代品?

C++ 链接错误。我究竟做错了什么?