15

有两种使用 lambda 函数变量的方法:

std::function<int(int, int)> x1 = [=](int a, int b) -> int{return a + b;};

//usage
void set(std::function<int(int, int)> x);
std::function<int(int, int)> get();

和:

std::function<int(int, int)>* x2 = new std::function<int(int, int)>([=](int a, int b) -> int{return a + b;});

//usage
void set(std::function<int(int, int)>* x);
std::function<int(int, int)>* get();

我想知道有什么区别,因为我不知道 lambda 函数数据是如何存储的。

我想知道在性能、内存使用方面的最佳方式以及将 lambda 函数作为参数传递或返回 lambda 函数的最佳方式。
如果 lambda 函数对象的大小大于 4 或避免错误,我更喜欢使用指针(如果在我执行归因时执行了某种复制构造函数,或者在我不想要时执行了某种析构函数) .

我应该如何声明 lambda 函数变量?

编辑

我想避免复制和移动,我想再次继续使用相同的功能。

我应该如何改变这个例子?

int call1(std::function<int(int, int)> f){
    return f(1, 2);
}
int call2(std::function<int(int, int)> f){
    return f(4, 3);
}
std::function<int(int, int)>& recv(int s){
    return [=](int a, int b) -> int{return a*b + s;};
}

int main(){
    std::function<int(int, int)> f1, f2;

    f1 = [=](int a, int b) -> int{return a + b;};
    f2 = recv(10);

    call1(f1);
    call2(f1);
    call1(f2);
    call2(f2);
}

我无法在函数 recv 中返回引用:

 warning: returning reference to temporary

这是一个好的解决方案吗?

int call1(std::function<int(int, int)>* f){
    return (*f)(1, 2);
}
int call2(std::function<int(int, int)>* f){
    return (*f)(4, 3);
}

std::function<int(int, int)>* recv(int s){
    return new std::function<int(int, int)>([=](int a, int b) -> int{return a*b + s;});
}

int main(){
    std::function<int(int, int)> f1 = [=](int a, int b) -> int{return a + b;};
    std::function<int(int, int)> *f2 = recv(10);

    call1(&f1);
    call2(&f1);
    call1(f2);
    call2(f2);

    delete f2;
}

编辑(结论)

lambda 函数对象就像是类实例的任何对象。分配、论据和归属的规则是相同的。

4

3 回答 3

22

创建 的指针是没有意义的std::function。它对你没有任何好处。它没有好处,反而给你带来了更多的负担,因为你必须删除记忆。

所以第一个版本(即非指针版本)就是你应该去的。

一般来说,根据经验,尽可能避免。 new

此外,您不需要std::function每次使用 lambda。很多时候,您可以只auto用作:

auto add = [](int a, int b) { return a + b;};

std::cout << add(100,100) << std::endl;
std::cout << add(120,140) << std::endl;
于 2012-04-14T16:55:36.993 回答
15

您已经明确表示要使用指针。所以……这样做。

但至少使用智能指针。正确使用std::shared_ptr可以避免很多问题。当然,您必须确保避免循环引用,但对于 lambdas,这似乎不太可能。

当然,这仍然是一个糟糕的主意。但这只是在作​​为一个完全没有意义的过早优化来让自己对传递事物感觉更好的意义上说是可怕的,而不是在代码的实际运行时间中获得任何实际可衡量的好处。至少使用shared_ptr,您不太可能意外删除内存或遇到异常安全问题。


我将忽略糟糕的 C++ 堆分配实践,您可以轻松地进行堆栈分配,以及在不使用智能指针的情况下跟踪此内存的痛苦。这将假设您知道自己在做什么,而是专注于您询问的“性能”和“内存使用”问题。

不要将此视为对您想做的事情的认可。

std::function通常会通过对某个对象进行内部堆分配来实现;由于类型擦除,这是必要的。所以它的大小至少是一个指针;可能更多。堆分配的大小可能是 vtable 指针 + lambda 类的大小。其大小取决于您捕获了多少东西以及它是按价值还是参考。

像大多数 C++ 标准库对象一样,std::function是可复制的。执行复制实际上将复制它,而不是在您现在有两个具有相同指针的对象的情况下进行假装复制。所以每个人都会有一个内部堆对象的独立副本。也就是说,复制它意味着进行另一个堆分配。这也将复制 lambda 本身,这意味着复制其中捕获的所有内容。

然而,像大多数 C++ 标准库对象一样,std::function可移动的。这不会进行任何内存分配。

因此,如果你想这样做,它非常便宜:

std::function<int(int, int)> x1 = [=](int a, int b) -> int{return a + b;};

//usage
void set(std::function<int(int, int)> x);
const std::function<int(int, int)> &get();

set(std::move(x1)); //x1 is now *empty*; you can't use it anymore.

您的set函数可以根据需要将其移动到自己的内部存储中。请注意,get现在返回一个const&; 这取决于这些功能的存储不会随处可见。

会有多便宜move?它可能相当于只复制 的字段std::function,以及空白或以其他方式中和原始字段。除非您使用的是性能关键代码,否则这将不是您应该关心的任何事情。

于 2012-04-14T17:04:42.543 回答
-1

以下是我倾向于使用这些新的 lambda 表达式(加上 std::function 和 std::bind)为您提供的“函数式 C++”的指导方针。随意忽略它们或按您认为合适的方式采用它们:

1) 将您想要命名的任何 lambda 分配为 std::function。如果您将它传递给某个函数“X(..)”并且不再查看它,请在函数调用中声明它,以便“X”可以利用它是临时的这一事实。

// I want a real name for this (plan to use multiple times, etc.)
// Rarely are you going to do this without moving the lambda around, so
// declaring it as 'auto' is pointless because C++ APIs expect a real type.

std::function<double(double)> computeAbs = [](double d) -> double { return fabs(d); };

// Here, we only ever use the lambda for the call to "X".
// So, just declare it inline. That will enforce our intended usage and probably
// produce more efficient code too.

X([](double r) -> double { return fabs(r); });

2) 如果您打算在各处移动函数,请在 std::function 周围使用某种引用计数包装器。这个想法是传递一个指向实际 std::function 的安全指针,从而避免昂贵的副本以及担心 new 和 delete。

为此构建您自己的类可能会很痛苦,但它会确保您只需将 lambda、函数 ptr 等实际转换为 std::function 一次。然后,您可以提供像“GetFunction”这样的 API 来为期望它们的 API 返回您按值携带的 std::function。

3) 对于采用 std::function 或 lambdas 的公共 API,只需按值传递。

是的,您可以通过引用甚至 r 值(那些'&&')引用来工作,在某些情况下它们甚至是正确的解决方案。但是 lambdas 令人讨厌,因为它们的实际类型对您来说实际上是不可知的。但是,它们都可以转换为 std::function,就像所有原始类型都可以转换为“int”一样,因此将它们传递给函数涉及使用相同的技巧,即让您将字符和浮点数传递给像“f(诠释a)”。

即,您创建函数“f”,这些函数要么在仿函数参数的 TYPE 上进行模板化,例如:

template<typename Functor>
double f(Functor absFunc);

或者,你让每个人都标准化为 std::function:

double f(std::function<double(double)> absFunc);

现在,当您调用“f([](double r) -> double { return fabs(r); })”时,编译器知道如何处理它。用户无需围绕您的 API 编写代码即可获得“正常工作”的东西,这正是您想要的。

于 2014-02-06T07:32:04.083 回答