我想制作一个状态机,它可以在自己的线程中处理提交的信号。我使用 Visual Studio 2015,因此支持 C++11 和部分 C++14。 信号存储在容器中。每个信号都表示为 std::function。 我想从客户端等待,直到状态机处理提交的信号,因此它是一种同步信号。
我的问题是:我无法将 std::promise 捕获到 lambda 中并将其添加到容器中。
#include <functional>
#include <future>
#include <list>
std::list<std::function<int()>> callbacks;
void addToCallbacks(std::function<int()>&& callback)
{
callbacks.push_back(std::move(callback));
}
int main()
{
std::promise<int> prom;
auto fut = prom.get_future();
// I have made the lambda mutable, so that the promise is not const, so that I can call the set_value
auto callback = [proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; };
// This does not compile
addToCallbacks(std::move(callback));
// This does not compile either, however this lambda is a temporal value (lvalue)
addToCallbacks([proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; });
return 0;
}
如果出现这种情况,有什么解决办法
如果能以某种方式将 Promise 嵌入到 lambda 生成的类中,那就太好了。这意味着 lambda 不再可复制,只能移动。有可能吗?
std::function
只能由可复制的函子构造。来自 [func.wrap.func.con]:
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
要求:
应为可复制构造。F
std::promise
是不可复制的,因此无法将具有此成员的函子粘贴到 std::function
中。期间。
鉴于您希望函子真正拥有承诺的所有权,这不会给您留下太多选择。差不多
std::shared_ptr<std::promise>
。任何其他选项要么不起作用(例如 std::unique_ptr<std::promise>
),要么给你留下一个悬空的对象(例如 std::reference_wrapper<std::promise>
),或者给你带来内存管理问题(例如 std::promise*
)。
但是,您可以使用
std::function
以外的其他内容。你可以看看Yakk的task
想法这里,以及dyp的function_mo
这里,它们都创造了可移动的std::function
风味。
创建自己的多态函数类很简单。此示例修复了参数和返回类型,但如果需要,可以做更多的工作来将它们模板化。
#include <iostream>
#include <functional>
#include <future>
#include <list>
// declare a non-polymorphic container for any function object that takes zero args and returns an int
// in addition, the contained function need not be copyable
class move_only_function
{
// define the concept of being callable while mutable
struct concept
{
concept() = default;
concept(concept&&) = default;
concept& operator=(concept&&) = default;
concept(const concept&) = delete;
concept& operator=(const concept&) = default;
virtual ~concept() = default;
virtual int call() = 0;
};
// model the concept for any given function object
template<class F>
struct model : concept
{
model(F&& f)
: _f(std::move(f))
{}
int call() override
{
return _f();
}
F _f;
};
public:
// provide a public interface
int operator()() // note: not const
{
return _ptr->call();
}
// provide a constructor taking any appropriate object
template<class FI>
move_only_function(FI&& f)
: _ptr(std::make_unique<model<FI>>(std::move(f)))
{}
private:
std::unique_ptr<concept> _ptr;
};
std::list<move_only_function> callbacks;
void addToCallbacks(move_only_function&& callback)
{
callbacks.push_back(std::move(callback));
}
int main()
{
std::promise<int> prom;
auto fut = prom.get_future();
// I have made the lambda mutable, so that the promise is not const, so that I can call the set_value
auto callback = [proms=std::move(prom)]() mutable { proms.set_value(5); return 5; };
// This now compiles
addToCallbacks(std::move(callback));
std::promise<int> prom2;
auto fut2 = prom2.get_future();
// this also compiles
addToCallbacks([proms = std::move(prom2)]() mutable { proms.set_value(6); return 6; });
for (auto& f : callbacks)
{
std::cout << "call returns " << f() << std::endl;
}
std::cout << "fut = " << fut.get() << std::endl;
std::cout << "fut2 = " << fut2.get() << std::endl;
return 0;
}
预期输出:
call returns 5
call returns 6
fut = 5
fut2 = 6
另一种简单的方法可能是使用破坏性复制习惯用法并将仅可移动类型包装成简单的CopyConstructible结构:
#include <functional>
#include <future>
#include <type_traits>
template <typename T>
struct destructive_copy_constructible
{
mutable T value;
destructive_copy_constructible() {}
destructive_copy_constructible(T&& v): value(std::move(v)) {}
destructive_copy_constructible(const destructive_copy_constructible<T>& rhs)
: value(std::move(rhs.value))
{}
destructive_copy_constructible(destructive_copy_constructible<T>&& rhs) = default;
destructive_copy_constructible&
operator=(const destructive_copy_constructible<T>& rhs) = delete;
destructive_copy_constructible&
operator=(destructive_copy_constructible<T>&& rhs) = delete;
};
template <typename T>
using dcc_t =
destructive_copy_constructible<typename std::remove_reference<T>::type>;
template <typename T>
inline dcc_t<T> move_to_dcc(T&& r)
{
return dcc_t<T>(std::move(r));
}
int main()
{
std::promise<int> result;
std::function<void()> f = [r = move_to_dcc(result)]()
{
r.value.set_value(42);
};
return 0;
}
尽管破坏性复制习语被认为是危险的并且被移动习语所淘汰,但它仍然有用,至少可以弥补这样的
std::function
漏洞。
与建议的 std::shared_ptr
和
move_only_function
解决方案相比,这里的优点是零开销(无复制/动态内存分配)。通过使用类似移动的语法来清楚地描述破坏性的复制/移动语义,可以大大减轻滥用复制的原始对象的危险。
另一个有用的副作用是,您不必声明 entire lambda 可变来设置
std::promise
值,因为它已经在 DCC 包装器中声明了。
另一个变体,如果你确定它不会是副本:
struct copyable_promise: std::promise< int >
{
copyable_promise() = default;
copyable_promise( copyable_promise&& ) = default;
copyable_promise& operator=( copyable_promise&& ) = default;
[[noreturn]] copyable_promise( const copyable_promise& src )
//or make dummy instead exception to just fix:
// : std::promise< int >( )
{
throw std::runtime_error( "copyable_promise copy constructor is not allowed" );
}
// copyable_promise& operator=( const copyable_promise& ) = delete;
};