繁体   English   中英

C++11 lambdas 如何表示和传递?

[英]How are C++11 lambdas represented and passed?

在 c++11 中传递 lambda 非常容易:

func( []( int arg ) {
  // code
} ) ;

但我想知道,将 lambda 传递给这样的函数的成本是多少? 如果 func 将 lambda 传递给其他函数会怎样?

void func( function< void (int arg) > f ) {
  doSomethingElse( f ) ;
}

lambda 的传递代价高昂吗? 由于一个function对象可以被赋值为 0,

function< void (int arg) > f = 0 ; // 0 means "not init" 

这让我认为函数对象有点像指针。 但是如果不使用new ,则意味着它们可能类似于值类型的struct或类,默认为堆栈分配和成员方式复制。

当您“按值”传递函数对象时,C++11“代码体”和捕获的变量组是如何传递的? 代码体是否有很多多余的副本? 我是否必须标记通过const&传递的每个function对象const&以便不制作副本:

void func( const function< void (int arg) >& f ) {
}

或者函数对象以某种方式传递与常规 C++ 结构不同?

免责声明:与现实相比,我的回答有些简化(我把一些细节放在一边),但大局就在这里。 此外,标准没有完全指定 lambdas 或std::function必须如何在内部实现(实现具有一定的自由度),因此,就像对实现细节的任何讨论一样,您的编译器可能会也可能不会完全按照这种方式进行。

但同样,这是一个与 VTables 非常相似的主题:标准没有太多要求,但任何明智的编译器仍然很可能这样做,所以我相信值得深入研究一下。 :)


拉姆达

实现 lambda 最直接的方法是一种未命名的struct

auto lambda = [](Args...) -> Return { /*...*/ };

// roughly equivalent to:
struct {
    Return operator ()(Args...) { /*...*/ }
}
lambda; // instance of the unnamed struct

就像任何其他类一样,当你传递它的实例时,你永远不必复制代码,只需复制实际数据(这里根本没有)。


按值捕获的对象被复制到struct

Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };

// roughly equivalent to:
struct Temporary { // note: we can't make it an unnamed struct any more since we need
                   // a constructor, but that's just a syntax quirk

    const Value v; // note: capture by value is const by default unless the lambda is mutable
    Temporary(Value v_) : v(v_) {}
    Return operator ()(Args...) { /*... use v, captured by value...*/ }
}
lambda(v); // instance of the struct

同样,传递它仅意味着您传递数据 ( v ) 而不是代码本身。


同样,通过引用捕获的对象被引用到struct

Value v;
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ };

// roughly equivalent to:
struct Temporary {
    Value& v; // note: capture by reference is non-const
    Temporary(Value& v_) : v(v_) {}
    Return operator ()(Args...) { /*... use v, captured by reference...*/ }
}
lambda(v); // instance of the struct

这几乎是 lambda 本身的全部(除了我省略的少数实现细节,但这些细节与理解它的工作原理无关)。


std::function

std::function是任何类型函子(lambda、独立/静态/成员函数、我展示的函子类等)的通用包装器。

std::function的内部结构非常复杂,因为它们必须支持所有这些情况。 根据函子的确切类型,这至少需要以下数据(给出或获取实现细节):

  • 指向独立/静态函数的指针。

要么,

  • 指向函子副本[请参阅下面的注释]的指针(动态分配以允许任何类型的函子,正如您正确指出的那样)。
  • 指向要调用的成员函数的指针。
  • 一个指向分配器的指针,它能够复制函子和它自己(因为可以使用任何类型的函子,指向函子的指针应该是void* ,因此必须有这样的机制——可能使用多态性. 基类 + 虚方法,派生类在template<class Functor> function(Functor)构造template<class Functor> function(Functor)本地生成)。

因为它事先不知道它必须存储哪种函子(这很明显,因为std::function可以重新分配),所以它必须处理所有可能的情况并在运行时做出决定。

注意:我不知道标准在哪里要求它,但这绝对是一个新副本,底层仿函数没有共享:

int v = 0;
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; };
std::function<void()> g = f;

f(); // 0
f(); // 1
g(); // 0
g(); // 1

因此,当您传递std::function ,它至少涉及这四个指针(实际上在 GCC 4.7 64 位sizeof(std::function<void()>是 32,这是四个 64 位指针)和可选的动态函子的分配副本(正如我已经说过的,它只包含捕获的对象,您不复制代码)。


回答问题

将 lambda 传递给这样的函数的成本是多少? [问题的背景:按价值]

好吧,正如您所看到的,它主要取决于您的函子(手工制作的struct函子或 lambda)及其包含的变量。 与直接按值传递struct functor 相比,开销可以忽略不计,但它当然比按引用传递struct functor 高得多。

我是否必须标记通过const&传递的每个函数对象const&以便不制作副本?

恐怕这很难以通用的方式回答。 有时您会希望通过const引用、有时通过值、有时通过右值引用传递,以便您可以移动它。 这实际上取决于您的代码的语义。

关于您应该选择哪一个的规则是一个完全不同的主题 IMO,请记住它们与任何其他对象相同。

无论如何,您现在拥有做出明智决定的所有关键(同样,取决于您的代码及其语义)。

另请参阅C++11 lambda 实现和内存模型

一个 lambda 表达式就是:一个表达式。 编译后,它会在运行时生成一个闭包对象。

5.1.2 Lambda 表达式 [expr.prim.lambda]

对 lambda 表达式的求值产生一个 prvalue 临时值 (12.2)。 这个临时对象称为闭包对象。

对象本身是实现定义的,可能因编译器而异。

这是在 clang https://github.com/faisalv/clang-glambda中 lambdas 的原始实现

如果 lambda 可以作为一个简单的函数(即它不捕获任何东西),那么它的制作方式完全相同。 特别是标准要求它与具有相同签名的旧式函数指针兼容。 [编辑:它不准确,请参阅评论中的讨论]

其余的取决于实施,但我不担心。 最直接的实现只是携带信息。 与您在捕获中要求的一样多。 所以效果和你手动创建一个类是一样的。 或者使用一些 std::bind 变体。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM