简体   繁体   English

在 lambda C++14 中捕获 std::promise

[英]Capture std::promise in a lambda C++14

I want to make a state machine which works processes the submitted signals in its own thread.我想制作一个状态机,它可以在自己的线程中处理提交的信号。 I use Visual Studio 2015, so C++11 and partially C++14 is supported.我使用 Visual Studio 2015,因此支持 C++11 和部分 C++14。 Signals are stored in containers.信号存储在容器中。 Each signal is represented as an std::function.每个信号都表示为一个 std::function。 I would like to wait from the client until the state machine processes the signal that was submitted, so it is a kind of synchronous signal.我想从客户端等待,直到状态机处理提交的信号,所以它是一种同步信号。

My problem is: I cannot capture an std::promise into a lambda and add it to the container.我的问题是:我无法将 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;
}

What are the solutions if如果有什么解决办法

  • I want to avoid capturing the promise by reference我想避免通过引用捕获承诺
  • I want to avoid capturing a * pointer or a shared_ptr to the promise我想避免将 * 指针或 shared_ptr 捕获到 promise

It would be nice to embed the promise into the class somehow that the lambda generates.将承诺以某种方式嵌入到 lambda 生成的类中会很好。 This means that the lambda is not copyable any more, only moveable.这意味着 lambda 不再可复制,只能移动。 Is it possible at all?有可能吗?

std::function can only be constructed from functors that are copyable . std::function只能由可复制的仿函数构造。 From [func.wrap.func.con]: 来自[func.wrap.func.con]:

 template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f); 

Requires : F shall be CopyConstructible . 要求F应为CopyConstructible

std::promise is non-copyable, so there's no way to stick a functor with this member into a std::function . std::promise是不可复制的,因此无法将带有此成员的std::function粘贴到std::function Period. 期。

Given that you want your functor to actually take ownership of the promise, this doesn't leave you many options. 鉴于您希望您的仿函数实际承担承诺的所有权,这不会给您留下很多选择。 Pretty much std::shared_ptr<std::promise> . 几乎是std::shared_ptr<std::promise> Any other option either doesn't work (eg std::unique_ptr<std::promise> ), leaves you with a dangling object (eg std::reference_wrapper<std::promise> ), or leaves you with memory-management issues (eg std::promise* ). 任何其他选项要么不起作用(例如std::unique_ptr<std::promise> ),请留下悬空对象(例如std::reference_wrapper<std::promise> ),否则会留下内存管理问题(例如std::promise* )。


You could, however, use something other than std::function . 但是,您可以使用除std::function之外的其他内容。 You can take a look at Yakk's task idea here , as well as dyp 's function_mo here , both of which create movable flavors of std::function . 你可以看看Yakk的task的想法在这里 ,以及DYPfunction_mo 这里 ,两者创造的活动口味std::function

It's trivial to roll your own polymorphic function class. 滚动自己的多态函数类是微不足道的。 This example fixes the argument and return types, but a little more work could templatise them if desired. 此示例修复了参数和返回类型,但如果需要,可以进行更多的工作。

#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;
}

expected output: 预期产量:

call returns 5
call returns 6
fut = 5
fut2 = 6

Another simple approach may be to use destructive copy idiom and wrap movable-only type into trivial CopyConstructible struct: 另一个简单的方法可能是使用破坏性复制习惯用法并将仅可移动类型包装到普通的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;
}

Despite the fact that destructive copy idiom is considered dangerous and obsoleted by move idiom, it still can be useful, at least to cover such std::function hole. 尽管破坏性复制习惯被认为是危险的并且被移动成语所废弃,但它仍然有用,至少可以覆盖这样的std::function hole。

Advantage here is zero overhead (no copy/dynamic memory allocations) comparing to proposed std::shared_ptr and move_only_function solutions. 与提出的std::shared_ptrmove_only_function解决方案相比,这里的优势是零开销(无复制/动态内存分配)。 And danger of misusing copied original object is mostly mitigated by using move-like syntax that clearly describes destructive copy/move semantics. 并且通过使用清楚地描述破坏性复制/移动语义的类似移动的语法来大大减轻滥用复制的原始对象的危险。

Another useful side effect is that you don't have to declare entire lambda mutable to set std::promise value because it's already declared so in DCC wrapper. 另一个有用的副作用是你不必声明整个 lambda mutable来设置std::promise值,因为它已经在DCC包装器中声明了。

Good news: C++23 will resolve that long-standing issue.好消息:C++23 将解决这个长期存在的问题。

In the (still evolving future) standard, there is a std::move_only_function , which will allow precisely such use cases as detailed here.在(仍在发展的未来)标准中,有一个std::move_only_function ,它将准确地允许此处详述的此类用例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM