带有动态存储持续时间的lambda

Lambda with dynamic storage duration

本文关键字:lambda 持续时间 存储 动态      更新时间:2023-10-16

根据cppreference.com,c 11 lambda文字语法仅在直接初始化中使用。似乎没有一种直接与new操作员直接使用lambda语法的方法。

我需要将lambda功能存储在堆中,以便可以在以后从其他线程调用它。很容易制作lambda的副本,但是是否有一种简单的方法可以直接在堆(动态存储持续时间)中分配lambda,而无需先分配堆栈(自动存储持续时间)并制作副本?

这是一个简单的示例:

#include <cstdio>
#include <cassert>
struct MyObj {
    int value;
    int copies;
    int moves;
    MyObj(int v): value(v), copies(0), moves(0) {
        printf("Created object with value %d.n", value);
    }
    MyObj(const MyObj &other): value(other.value),
    copies(other.copies+1), moves(other.moves) { }
    MyObj(const MyObj &&other): value(other.value),
    copies(other.copies), moves(other.moves+1) { }
};
int main (int argc, char **argv) {
    MyObj o { 5 };
    // Create lambda on stack (automatic storage duration)
    auto f = [o] {
        printf("Object value is %dn", o.value);
        printf("%d copies, %d moves...n", o.copies, o.moves);
    };
    // Copy lambda to heap (dynamic storage duration)
    decltype(f) *g = new decltype(f)(f);
    // Call the copy
    (*g)();
    return 0;
}

上面的程序制作了2个o的副本(一个在捕获中,另一个是将lambda复制到堆中时)。理想情况下,将只有一个副本或移动,当堆分配的Lambda捕获o的副本时,会发生这种情况。

在C 11中,lambda表达式将以某种形式的自动对象(无论是堆栈变量还是未命名的临时性)导致某种形式的自动对象。您无能为力。

在C 17中,保证的能力使我们能够做到这一点:

new auto(<lambda>)

这使用new分配的内存来存储该表达式的结果。这里不会创建临时的lambda对象,也不会调用lambda的任何副本/移动构造函数。最重要的是,该语言不需要lambda type 具有可以调用的复制/移动构造函数。

需要保证省略以确保这一点。如果没有保证,那么您就可以在编译器上进行优化。标准允许此类情况填写副本。是的,任何值得使用的编译器都可能会出现此类副本。

有了保证的责任,您可以捕获固定的类型,这仍然可以在不复制任何内容的情况下起作用。pre-c 17,即使对其进行调用,您的lambda仍然需要具有副本或移动构造函数。

auto关键字在new表达式中是合法的,它允许您执行此操作:

    // Create lambda directly in heap (dynamic storage duration)
    auto g = new auto([o] {
        printf("Object value is %dn", o.value);
        printf("%d copies, %d moves...n", o.copies, o.moves);
    });

这是整个(更新)示例:

#include <cstdio>
#include <cassert>
struct MyObj {
    int value;
    int copies;
    int moves;
    MyObj(int v): value(v), copies(0), moves(0) {
        printf("Created object with value %d.n", value);
    }
    MyObj(const MyObj &other): value(other.value),
    copies(other.copies+1), moves(other.moves) { }
    MyObj(const MyObj &&other): value(other.value),
    copies(other.copies), moves(other.moves+1) { }
};
int main (int argc, char **argv) {
    MyObj o { 5 };
    // Create lambda directly in heap (dynamic storage duration)
    auto g = new auto([o] {
        printf("Object value is %dn", o.value);
        printf("%d copies, %d moves...n", o.copies, o.moves);
    });
    // Call heap lambda
    (*g)();
    return 0;
}

以上仅制作一份o的副本,至少在我的平台上(Apple LLVM版本7.0.0(Clang-700.1.76))。

您可以考虑类似STD :: make_unique之类的东西:

template <typename Lambda>
std::unique_ptr<Lambda> make_unique_lambda(Lambda&& lambda)
{
    return std::unique_ptr<Lambda>(
        new Lambda(std::forward<Lambda>(lambda))
    );
}
auto unique_lambda = make_unique_lambda([] () {
    // ...
});

可以在线程上移动的多态性函数容器是一个std ::函数,可以通过lambda进行直接启动。

int main (int argc, char **argv) {
    std::function<void()> g = [o = MyObj{5}]{
        printf("Object value is %dn", o.value);
        printf("%d copies, %d moves...n", o.copies, o.moves);
    };
    // Call the copy
    g();
    // now *move* it to another thread and call it there
    std::thread t([my_g = std::move(g)] { my_g(); });
    t.join();
    return 0;
}

预期:

Created object with value 5.
Object value is 5
0 copies, 1 moves...
Object value is 5
0 copies, 1 moves...