如何将unique_ptr捕获到lambda表达式中

How to capture a unique_ptr into a lambda expression?

本文关键字:lambda 表达式 ptr unique      更新时间:2023-10-16

我尝试了以下方法:

std::function<void ()> getAction(std::unique_ptr<MyClass> &&psomething){
    //The caller given ownership of psomething
    return [psomething](){ 
        psomething->do_some_thing();
        //psomething is expected to be released after this point
    };
}

但是它不能编译。什么好主意吗?

更新:

根据建议,需要一些新的语法来明确指定我们需要将所有权转移到lambda,我现在正在考虑以下语法:

std::function<void ()> getAction(std::unique_ptr<MyClass> psomething){
    //The caller given ownership of psomething
    return [auto psomething=move(psomething)](){ 
        psomething->do_some_thing();
        //psomething is expected to be released after this point
    };
}

会是一个好的候选人吗?

更新1:

我将展示我的movecopy的实现如下:

template<typename T>
T copy(const T &t) {
    return t;
}
//process lvalue references
template<typename T>
T move(T &t) {
    return std::move(t);
}
class A{/*...*/};
void test(A &&a);
int main(int, char **){
    A a;
    test(copy(a));    //OK, copied
    test(move(a));    //OK, moved
    test(A());        //OK, temporary object
    test(copy(A()));  //OK, copying temporary object
    //You can disable this behavior by letting copy accepts T &  
    //test(move(A())); You should never move a temporary object
    //It is not good to have a rvalue version of move.
    //test(a); forbidden, you have to say weather you want to copy or move
    //from a lvalue reference.
}

c++ 14中的lambda通用捕获解决了这个问题:

// a unique_ptr is move-only
auto u = make_unique<some_type>(some, parameters); 
// move the unique_ptr into the lambda
go.run([u = move(u)]{do_something_with(u);});

不能永久捕获lambda中的unique_ptr。实际上,如果你想永久捕获lambda中的任何内容,它必须是可复制的;仅仅活动是不够的。

这可能被认为是c++ 11中的一个缺陷,但是您需要一些语法来显式地表示您希望将unique_ptr值移动到lambda中。c++ 11规范的措辞非常谨慎,以防止对命名变量的隐式移动;这就是std::move存在的原因,这是一个好的事情。

要做你想做的事情,要么使用std::bind(这将是半卷积的,需要binds的短序列),要么只是返回一个常规的旧对象。

同样,永远不要用unique_ptr代替&&,除非你真的在写它的move构造函数。只要按值取;用户可以按值提供它的唯一方法是使用std::move。实际上,永远不要通过&&获取任何东西,除非您正在编写move构造函数/赋值操作符(或实现转发函数)。

Nicol Bolas提到的使用std::bind的"半复杂"解决方案毕竟不是那么糟糕:

std::function<void ()> getAction(std::unique_ptr<MyClass>&& psomething)
{
    return std::bind([] (std::unique_ptr<MyClass>& p) { p->do_some_thing(); },
                     std::move(psomething));
}

对我来说,一个次优的解决方案是将unique_ptr转换为shared_ptr,然后在lambda中捕获shared_ptr

std::function<void()> getAction(std::unique_ptr<MyClass> psomething)
{
    //The caller given ownership of psomething
    std::shared_ptr<MyClass> psomethingShared = std::shared_ptr<MyClass>(std::move(psomething));
    return [psomethingShared]()
    {
        psomethingShared->do_some_thing();
    };
}

我使用了这个非常狡猾的解决方案,其中包括将unique_ptr粘贴在shared_ptr内。这是因为我的代码需要unique_ptr(由于API限制),所以我无法将其实际转换为shared_ptr(否则我将永远无法获得我的unique_ptr)。

我使用这个讨厌的东西的理由是,它是为我的测试代码,我必须在测试函数调用中std::bind一个unique_ptr

// Put unique_ptr inside a shared_ptr
auto sh = std::make_shared<std::unique_ptr<Type>>(std::move(unique));
std::function<void()> fnTest = std::bind([this, sh, input, output]() {
    // Move unique_ptr back out of shared_ptr
    auto unique = std::move(*sh.get());
    // Make sure unique_ptr is still valid
    assert(unique);
    // Move unique_ptr over to final function while calling it
    this->run_test(std::move(unique), input, output);
});

现在调用fnTest()将调用run_test()同时传递unique_ptr给它。第二次调用fnTest()将导致断言失败,因为unique_ptr在第一次调用期间已经被移动/丢失。

还需要知道,lambda捕获unique_ptr不能转换为std::function,因为std::function要求可调用对象是可复制的。

auto lambdaWithoutCapture = [](){return 1;}; //Can be std::function
auto lambdaWithCapture = [=](){return 1;}; //Can be std::function
auto lambdaWithCapture2 = [&](){return 1;}; //Can be std::function
auto lambdaWithCapture3 = [uptrProblematic = std::move(uptrProblematic)]() mutable {return 1;}; //Can't be std::function
因此,如果不需要指定函数的返回类型,可以使用不使用std::function的方法。但你需要知道,这只在局部范围内有效。你不能在头文件中声明auto workerFactory();,因为这会引发编译错误。
auto workerFactory()
{
    std::unique_ptr uptrProblematic = std::make_unique<int>(9);
    int importantData = 71;
    return [=, uptrProblematic = std::move(uptrProblematic)](std::string input) mutable -> int {
        std::cout << "Problematic variable is equal to: " << *uptrProblematic << "n";
        std::cout << "Important data is equal to: " << importantData << "n";
        std::cout << "Input equal to: " << input << "n";
        return 9;
    };
}
int main()
{
    auto worker = workerFactory();
    worker("Test");
}