繁体   English   中英

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

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

我想制作一个状态机,它可以在自己的线程中处理提交的信号。 我使用 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;
}

如果有什么解决办法

  • 我想避免通过引用捕获承诺
  • 我想避免将 * 指针或 shared_ptr 捕获到 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应为CopyConstructible

std::promise是不可复制的,因此无法将带有此成员的std::function粘贴到std::function 期。

鉴于您希望您的仿函数实际承担承诺的所有权,这不会给您留下很多选择。 几乎是std::shared_ptr<std::promise> 任何其他选项要么不起作用(例如std::unique_ptr<std::promise> ),请留下悬空对象(例如std::reference_wrapper<std::promise> ),否则会留下内存管理问题(例如std::promise* )。


但是,您可以使用除std::function之外的其他内容。 你可以看看Yakk的task的想法在这里 ,以及DYPfunction_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 hole。

与提出的std::shared_ptrmove_only_function解决方案相比,这里的优势是零开销(无复制/动态内存分配)。 并且通过使用清楚地描述破坏性复制/移动语义的类似移动的语法来大大减轻滥用复制的原始对象的危险。

另一个有用的副作用是你不必声明整个 lambda mutable来设置std::promise值,因为它已经在DCC包装器中声明了。

好消息:C++23 将解决这个长期存在的问题。

在(仍在发展的未来)标准中,有一个std::move_only_function ,它将准确地允许此处详述的此类用例。

暂无
暂无

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

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