C++;规范允许通过传递堆栈帧地址偏移实现闭包吗? 我目前没有访问C++语言规范的权限,但非权威的CpPaseCy.com网站说:

C++;规范允许通过传递堆栈帧地址偏移实现闭包吗? 我目前没有访问C++语言规范的权限,但非权威的CpPaseCy.com网站说:,c++,lambda,C++,Lambda,lambda表达式构造唯一的未命名非并集非聚合类型的未命名prvalue临时对象,称为闭包类型 我知道该规范还规定,只有未捕获的lambda可以分解为函数指针(复制自): 没有lambda捕获的lambda表达式的闭包类型具有一个公共非虚拟非显式const转换函数,该函数指向与闭包类型的函数调用运算符具有相同参数和返回类型的函数指针。此转换函数返回的值应为函数的地址,该函数在调用时与调用闭包类型的函数调用运算符具有相同的效果 在C++和C++中,一个具有可变捕获(即闭包)的lambda方法在使用

lambda表达式构造唯一的未命名非并集非聚合类型的未命名prvalue临时对象,称为闭包类型

我知道该规范还规定,只有未捕获的lambda可以分解为函数指针(复制自):

没有lambda捕获的lambda表达式的闭包类型具有一个公共非虚拟非显式const转换函数,该函数指向与闭包类型的函数调用运算符具有相同参数和返回类型的函数指针。此转换函数返回的值应为函数的地址,该函数在调用时与调用闭包类型的函数调用运算符具有相同的效果

<>在C++和C++中,一个具有可变捕获(即闭包)的lambda方法在使用时看起来是这样的(警告:C→/c++风格的伪代码前面):

这大致相当于这样做:

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;
        }
    }
}
注意
Closure::Action
方法中隐藏的
this
指针/引用参数。在C语言中,所有函数指针都是“代码> >委托< /代码>类型,它总是能够处理<代码>这个成员,但是在C++中,这意味着只能使用lambda变量捕获,使用<代码> STD::函数< /C>参数(我理解,有<代码>这个< /代码>参数)-或者当使用lambda作为C风格函数指针参数的参数时,不能使用变量捕获,并且不捕获意味着没有闭包,这意味着不需要
参数

但我注意到,在许多情况下,使用这个匿名闭包值是信息论上的空间浪费,因为必要的闭包信息已经在堆栈上了——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;
}
在堆栈不可执行的情况下,可以在单独的堆栈策略分配器中分配短
包装器
动态函数(鉴于其寿命受到父
a常规方法
函数的寿命的严格限制),该分配器具有标记为写入和执行的内存页

现在回答我的问题:


我解释了实现lambda变量捕获和闭包的替代策略-并且鉴于我个人认为这是可行的-<强>为什么C++规范禁止用C样式函数指针参数参数来使用lambdas变量捕获而不是离开它已定义实施?这个策略是不可行的还是有其他缺点(尽管我相信它在可重入和递归调用场景中也能工作)操作器()/<代码>。 如果可以证明,您从未以要求它成为对象的方式与它交互,那么它就不必存在。消除内嵌lambdas在C++中既方便又通用。 现在,带有捕获的lambda可能无法转换为裸函数指针。即使它最终什么也没抓到。它不应该有运算符转换为函数指针

作为一般规则,C++编译器可能不添加代码请求的堆分配;堆分配可能会在运行时失败,操作会在可观察的行为中抛出内存不足错误。因此,分配操作的行为与未分配操作的行为不同。只有当标准允许实现(如果操作可以抛出)时,才允许添加堆分配。您可以分配、捕获任何异常,并有一个备份计划(甚至有一个算法的性能要求基本上都需要该实现)


简而言之,创建动态函数并不是C++所禁止的,但它必须像捕获状态一样。也不禁止使用公共偏移指针替换lambda的
。如果通过引用捕获了引用,则此公共偏移指针无效;通常不起作用,因为lambda引用是指原始数据(而不是捕获的引用),在创建lambda时,原始数据与堆栈帧之间没有静态偏移


<> > C++中的引用具有非常灵活的运行时存在性。它们几乎没有内存“存在”和布局要求,并且很容易在不存在的情况下进行优化。在我的知识中,C++中的一组引用与静态互偏移量转换成一个指针是合法的。然而,我不知道有哪个编译器会尝试;通常,当你知道这么多的时候,你完全是一个远离引用的婴儿。

“我目前没有进入C++语言规范”你注意到你引用的网站有这个部分可以通过下端链接外部链接:谷歌“N4140”。要找到最新的规格,您是否可以在规格中查找它并不重要。这是一个有趣而有用的问题。“为什么C++规范禁止使用C样式函数指针参数参数的lambdas变量捕获”。哪里你想到C++规范的哪一部分?这个问题不是很清楚。什么是“C风格函数指针参数参数”?堆栈框架是一个实现细节,标准根本没有提到它们,因此它当然不关心编译器如何实现这些要求。依我看,编译器可能做的最大的事情就是完全优化lambda(即内联父函数)
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;
}
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
}