Lambda具有动态存储持续时间

时间:2016-06-20 14:23:49

标签: c++ c++11 lambda

根据cppreference.com,C ++ 11 lambda文字语法仅适用于 direct initialization 。似乎没有办法直接使用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 %d\n", 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;
}

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

4 个答案:

答案 0 :(得分:8)

在C ++ 11中,lambda表达式总是导致某种形式的自动对象,无论是堆栈变量还是未命名的临时对象。你无能为力改变它。

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

new auto(<lambda>)

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

需要保证省略以确保这一点。如果没有这种保证,那么您将依赖编译器来优化它。标准允许此类案例忽略副本。是的,任何值得使用的编译器都可能会忽略这些副本。

有了保证的elision,你可以捕获不动的类型,这仍然可以在不复制任何东西的情况下工作。在C ++之前的版本17中,你的lambda仍然需要一个复制或移动构造函数,即使它的调用被省略了。

答案 1 :(得分:7)

auto关键字在new表达式中是合法的,您可以这样做:

    // Create lambda directly in heap (dynamic storage duration)
    auto g = new auto([o] {
        printf("Object value is %d\n", 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 %d\n", 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))。

答案 2 :(得分:4)

您可以考虑使用类似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([] () {
    // ...
});

答案 3 :(得分:1)

可以在线程之间移动的多态函数容器是一个std :: function,它可以由lambda直接初始化。

int main (int argc, char **argv) {
    std::function<void()> g = [o = MyObj{5}]{
        printf("Object value is %d\n", 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...