簡體   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