future_error详解

引言

遇到这个问题以后第一次没有得到解答 随后经测试和查阅资料知道了它的由来 同时列出三种导致抛出future_error的原因 希望能有相同问题的朋友能得到帮助

什么是future_error

在这里插入图片描述

这是官网对于这个类型的解释

The class std::future_error defines an exception object that is thrown on failure by the functions in the thread library that deal with asynchronous execution and shared states

我们可以看到它是在处理异步程序和shared 状态失败的时候会被抛出.而且它本身是一个各个系统上实现不一致的东西,即ub

我们来看看官网的演示demo

#include <future>
#include <iostream>
 
int main()
{
    std::future<int> empty;
    try {
        int n = empty.get(); // The behavior is undefined, but
                             // some implementations throw std::future_error
    } catch (const std::future_error& e) {
        std::cout << "Caught a future_error with code \"" << e.code()
                  << "\"\nMessage: \"" << e.what() << "\"\n";
    }
}

我们可以看到在future的状态不是ready的时候我们进行get操作是ub,但是一些实现则会抛错,那么还有哪些情况会抛错呢

我们来简单的看看future的实现

      void
      set_value(const _Res& __r)
      { _M_future->_M_set_result(_State::__setter(this, __r)); }

      void
      set_value(_Res&& __r)
      { _M_future->_M_set_result(_State::__setter(this, std::move(__r))); }

      void
      set_exception(exception_ptr __p)
      { _M_future->_M_set_result(_State::__setter(__p, this)); }

		--------------------------------------------------------------------

      _M_set_result(function<_Ptr_type()> __res, bool __ignore_failure = false)
      {
	bool __did_set = false;
        // all calls to this function are serialized,
        // side-effects of invoking __res only happen once
	call_once(_M_once, &_State_baseV2::_M_do_set, this,
		  std::__addressof(__res), std::__addressof(__did_set));
	if (__did_set)
	  // Use release MO to synchronize with observers of the ready state.
	  _M_status._M_store_notify_all(_Status::__ready,
					memory_order_release);
	else if (!__ignore_failure)
          __throw_future_error(int(future_errc::promise_already_satisfied));
      }

我们可以看到在设置值的过程中函数都调用了M_set_result这个函数,而这个函数中使用了call_once,这个函数第一个参数是once_flag类型的,作用是这个函数在这段代码中代码仅调用一次,之后调用失败,

      void
      _M_do_set(function<_Ptr_type()>* __f, bool* __did_set)
      {
        _Ptr_type __res = (*__f)();
        // Notify the caller that we did try to set; if we do not throw an
        // exception, the caller will be aware that it did set (e.g., see
        // _M_set_result).
	*__did_set = true;
        _M_result.swap(__res); // nothrow
      }

我们可以看到如果成功执行了call_once,也就是第一次调用call_once,就会使dis_set为true,从而进入第一个判断条件,但是第后面调用的时候就会进入第二个判断条件,直接抛出一个future_error的错误,这也是一个抛错的时机 下面是一个demo

void test(std::promise<int>& para){
    para.set_value(10);
    para.set_value(20);
    return;
}

int main(){
    std::promise<int> pro;
    std::future<int> T = pro.get_future();
    std::thread fun(test, std::ref(pro));
    fun.join();
    cout << T.get() << endl;
    return 0;
}
terminate called after throwing an instance of 'std::future_error'
  what():  std::future_error: Promise already satisfied
[1]    12731 abort      ./a.out

随着两次的set_value 当然会抛错了.

再来看一种情况

template<typename _Tp>
  static void
  _S_check(const shared_ptr<_Tp>& __p)
  {
    if (!static_cast<bool>(__p))
      __throw_future_error((int)future_errc::no_state);
  }

我们可以看到这个函数中也会抛出future_error 且判断条件只有一句,很巧,找到了另一个成员函数

bool
valid() const noexcept { return static_cast<bool>(_M_state); }

在官网上我们找到了这样一段解释

Checks if the future refers to a shared state.
This is the case only for futures that were not default-constructed or moved from (i.e. returned by std::promise::get_future(), std::packaged_task::get_future() or std::async()) until the first time get() or share() is called.
The behavior is undefined if any member function other than the destructor, the move-assignment operator, or valid is called on a future that does not refer to shared state (although implementations are encouraged to throw std::future_error indicating no_state in this case). It is valid to move from a future object for which valid() is false

检测future是否指向共享状态
这仅适用于为非默认构造的或是在第一次执行get或者share之前未移动的future(返回 std::promise::get_future(), std::packaged_task::get_future() 或者std::async()算是移动),
如果在未引用共享状态的将来调用除析构函数,移动运算符或valid以外的任何成员函数,则该行为未定义(),尽管在这种情况下有些实现会鼓励抛出一个future_error来指示no_state,从valid()返回为false的对象移动的对象是有效的.

下面一个简单的小demo来模拟这种情况

void test(std::promise<int>& para){
    para.set_value(10);
    //para.set_value(20);
    return;
}

int main(){
    std::promise<int> pro;
    std::future<int> T = pro.get_future();
    std::thread fun(test, std::ref(pro));
    fun.join();
    cout << T.get() << endl;
    auto Error_T = pro.get_future();
    return 0;
}
10
terminate called after throwing an instance of 'std::future_error'
  what():  std::future_error: Future already retrieved
[1]    14518 abort      ./a.out

在get后执行了get_future 抛出了错误

参考
https://en.cppreference.com/w/cpp/thread/future_error

posted @ 2022-07-02 13:18  李兆龙的博客  阅读(244)  评论(0编辑  收藏  举报