我目前无法访问C++语言规范,但非权威的cppreference.com网站说:
http://en.cppreference.com/w/cpp/language/lambda
lambda表达式构造一个未命名的prvalue临时对象,该对象具有唯一的非命名非联合非聚合类型,称为闭包类型.
我知道规范还指出只有非捕获的lambdas可能会分解为函数指针(从正面的lambda复制:'+ [] {}' - 这是什么巫术?):
没有lambda-capture的lambda表达式的闭包类型有一个公共的非虚拟非显式const转换函数,用于指向具有与闭包类型的函数调用操作符相同的参数和返回类型的函数.此转换函数返回的值应为函数的地址,该函数在调用时与调用闭包类型的函数调用运算符具有相同的效果.
在C#和C++中,带有变量捕获的lambda方法(即闭包)在使用时看起来像这样(警告:C#/ C++ - 样式伪代码):
class Foo {
void AConventionalMethod() {
String x = null;
Int32 y = 456;
y = this.arrayOfStringsField.IndexOfFirstMatch( (element, index) => {
x = this.DoSomethingWithAString( element );
y += index;
return index > 5; // bool return type
} );
}
}
Run Code Online (Sandbox Code Playgroud)
这可以被认为大致相当于这样做:
class Closure {
Foo foo;
String x;
Int32 y;
Boolean Action(String element, Int32 index) {
this.x = this.foo.DoSomethingwithAString( element );
this.y += index;
return index > 5;
}
}
class Foo {
void AConventionalMethod() {
String x = null;
Int32 y = 456;
{
Closure closure = new Closure() { foo = this, x = x, y = y };
Int32 tempY = this.arrayOfStringsField.IndexOfFirstMatch( closure.Action ); // passes `closure` as `this` inside the Delegate
x = closure.x;
y = closure.y;
y = tempY;
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意方法中隐藏的this指针/引用参数Closure::Action.这在C#中很好,其中所有函数指针都是Delegate可以始终处理this成员的类型,但在C++中它意味着你只能使用lambda变量捕获与std::function参数(我理解,有this参数) - 或者使用lambda作为一个C风格的函数指针参数的参数,你不能使用变量捕获,没有捕获意味着没有闭包,这意味着不需要this参数.
但我注意到在许多情况下使用这个匿名闭包值是信息理论上的空间浪费,因为必要的闭包信息已经在堆栈上 - 所有lambda函数需要的是被调用者的堆栈地址,它可以从访问并改变那些捕获的变量.在使用this-saving Delegate或者std::function使用的情况下,隐藏参数只需要存储被调用者的堆栈帧的内存地址:
void AConventionalMethod() {
void* frame;
frame = &frame; // get current stack frame 'base address'
string x = nullptr;
int32_t y = 456;
y = this.arrayOfStrings( lambdaAction );
}
static bool lambdaAction(void* frame, string element, int32_t index) {
Foo* foo = reintepret_cast<Foo*>( frame + 0 );
string* x = reintepret_cast<string*>( frame + 4 ); // the compiler would know what the `frame + n` offsets are)
int32_t* y = reintepret_cast<int32_t*>( frame + 8 );
*x = foo->doSomethingWithAString( element );
*y = *y + index;
return index > 5;
}
Run Code Online (Sandbox Code Playgroud)
但是上面没有改进任何东西:这里的信息等价与闭包示例相同,除了Closure类型存储实际的完整地址或值,这将指针算术保存在lambdaAction函数内 - 这种方法仍然需要(ab)使用隐藏参数.
但是当我看到你所需要的只是传递一个值:一个内存地址时,我意识到地址可以写成新发出的包装函数中的文字值,然后调用原始的lambda.如果执行环境允许堆栈上的可执行代码(在当今环境中不太可能,但在受约束或裸机系统或内核模式代码中可能),则此发出的函数甚至可能存在于堆栈中:
void AConventionalMethod() {
void* frame;
frame = &frame;
String x = null;
Int32 y = 456;
// the below code would actually be generated by the compiler:
char wrapper[] = __asm {
push frame ; does not alter any existing arguments
; but pushes/adds 'frame' as a new
; argument, and in a right-to-left calling-
; convention order this means that 'frame'
; becomes the first argument for 'lambdaAction'
call lambdaAction
return ; EAX return value preserved
};
y = a_higher_order_function_with_C_style_function_pointer_parameter( wrapper ); // a
}
static bool lambdaAction(void* frame, string element, int32_t index) {
// same as previous example's lambdaAction
}
Run Code Online (Sandbox Code Playgroud)
如果堆栈是不可执行的,则wrapper可以在单独的堆栈策略分配器中分配短动态函数(假设它的生命周期严格受父AConventionalMethod函数的生命周期限制),其具有标记用于写入和执行的存储器页面.
现在我的问题:
鉴于我已经解释了实现lambda变量捕获和闭包的替代策略 - 并且我个人认为这是可行的 - 为什么C++规范禁止使用C样式函数指针参数参数的lambdas的变量捕获而不是离开它实现定义了吗?这种策略是否不可行或是否存在其他缺点(尽管我认为它可以在重入和重新调用方案中使用)?
C++标准要求具有状态行为的lambda - 如果它是一个未指定大小的对象operator(),可以在其上调用它.
如果您可能永远不会以需要它为对象的方式与之交互,则它不需要存在.消除内联lambda在C++中既简单又常见.
现在,带捕获的lambda 可能无法转换为裸函数指针.即使它最终没有捕获任何东西.它不应该有一个操作符转换为函数指针.
作为一般规则,C++编译器可能不会添加代码未请求的堆分配; 堆分配在运行时可能会失败,并且操作会在可观察行为中抛出内存不足错误.因此,分配操作不会表现为 - 如果它没有分配.只有当标准使其对实现开放时,如果操作可以抛出,则允许添加堆分配.您可以分配,捕获任何异常并制定备份计划(甚至还有一种算法,其性能要求基本上要求实现).
简而言之,创建动态函数以执行不会被C++禁止,但它必须表现为 - 如果您捕获状态.使用公共偏移指针替换lambda this也不会被禁止.如果通过引用捕获引用,则该公共偏移指针赢得; t通常起作用,因为lambda引用是原始数据(不是捕获的引用),其在lambda创建时不与堆栈帧静态偏移.
C++中的引用具有非常灵活的运行时存在.它们具有很少的内存"存在"和布局要求,并且很容易优化.根据我的知识,在C++中将具有静态相互偏移的一堆引用转换为一个指针是合法的.然而,我不知道尝试的编译器; 通常当你知道那么多的时候,你就完全放弃了参考文献.