std::future 在调用 get() 后仍然有效(抛出异常)

std::future still valid after calling get() (which throws an exception)

根据 cppreference,调用 std::future::get 后:

valid() is false after a call to this method.

此外,来自 cplusplus.com

Once the shared state is ready, the function unblocks and returns (or throws) releasing its shared state. This makes the future object no longer valid: this member function shall be called once at most for every future shared state.

并且在异常安全下:

The function throws the exception stored in the shared state when the provider makes it ready by setting it to an exception. Note that in this case a basic guarantee is offered, with the future object being modified to no longer be a valid future (which is itself a valid state for object of this type, despite its name).

两个描述都没有区分对 get 的调用,returns 一个值和一个抛出关于未来对象失效的异常的值。

但是,所描述的行为并不是我在这个示例代码中看到的:

#include <chrono>
#include <future>
#include <iostream>
#include <stdexcept>


int foo()
{
    throw std::runtime_error("foo exception");
}


int main()
{
    std::future<int> futInt;

    futInt = std::async(std::launch::async, []() { return foo(); });

    while( !(futInt.valid() && futInt.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) )
        ;

    if( futInt.valid() )
    {
        int val;
        try
        {
            val = futInt.get();
        }
        catch( const std::exception& e )
        {
            std::cout << e.what() << std::endl;
        }
    }

    if( futInt.valid() )
    {
        std::cout << "This is TOTALLY UNEXPECTED!!!" << std::endl;
    }
    else
    {
        std::cout << "This is expected." << std::endl;
    }

    return 0;
}

我看到的输出是:

foo exception
This is TOTALLY UNEXPECTED!!!

我正在使用 Visual Studio Premium 2013,版本 12.0.30501.00 Update 2。这是编译器的错误,还是在出现异常时的正确行为?我一直无法找到与此相关的任何错误报告,因此不确定这是否是预期的行为。

编辑 - <未来> 实施调查

深入研究 std::future 实现,_Associated_state 对象被标记为 _Retrieved = true; AFTER 检查并抛出相关异常(如果任何):

virtual _Ty& _Get_value(bool _Get_only_once)
    {   // return the stored result or throw stored exception
    unique_lock<mutex> _Lock(_Mtx);
    if (_Get_only_once && _Retrieved)
        _Throw_future_error(
            make_error_code(future_errc::future_already_retrieved));
    if (_Exception)
        _Rethrow_future_exception(_Exception);
    _Retrieved = true;
    _Maybe_run_deferred_function(_Lock);
    while (!_Ready)
        _Cond.wait(_Lock);
    if (_Exception)
        _Rethrow_future_exception(_Exception);
    return (_Result);
    }

我的猜测是异常检查和 _Retrieved = true; 应该交换 - 对象应该立即设置为检索(在 _Get_only_once 检查之后),然后所有其他逻辑应该遵循。因此,编译器错误。

编辑 - 解决方法

我认为以下应该足以代替直接调用 get 直到实施修复:

template<typename T>
T getFromFuture(std::future<T>& fut)
{
    try
    {
        return fut.get();
    }
    catch( ... )
    {
        fut = {};
        throw;
    }
}

我在 Linux 上用 gcc 5.2.0 和 clang 3.7.0 编译——两次都是 64 位。 运行 程序总是导致

foo exception
This is expected.

在我看来,Visual 2013 对此处理不当。 另见:

C++ §30.6.6/16-17

Throws: the stored exception, if an exception was stored in the shared state.

Postcondition: valid() == false.

后置条件在抛出之后提到,因此必须始终成立,即使抛出异常也是如此。至少这是我的解释,虽然我不会说标准语。

我猜你也应该尝试使用 Visual Studio 2015 并报告一个错误,如果它显示相同的处理。