【问题标题】:Using lambda in default initializer vs using member function在默认初始化程序中使用 lambda 与使用成员函数
【发布时间】:2017-05-05 13:02:06
【问题描述】:

在默认初始化程序中使用“一次性”lambda 和使用普通的旧成员函数有什么区别吗?

struct A
{
    int i;
    int j = [&]
    // something non-trivial,
    // that requires multiple
    // statements and depends
    // on upper data members
    {
        int f = 0;
        for (int k = 0; k < i; ++k) {
            f += k;
        }
        return f;
    }();
    A(int k) : i(k) { ; }
};

对比:

struct A
{
    int i;
    int J() const
    {
        int f = 0;
        for (int k = 0; k < i; ++k) {
            f += k;
        }
        return f;
    }
    int j = J();
    A(int k) : i(k) { ; }
};

我唯一看到的是第二种方法的缺点:这里将额外的符号 J 引入到类 A 的命名空间中。还有其他区别吗?

【问题讨论】:

  • 就我个人而言,我会使用一个函数并在成员初始化列表中传入您需要计算的变量。否则,您需要依靠人们从不改变类中变量的顺序来保持它的工作。
  • 您的带有 lambda 初始化的示例无法编译。
  • @vordhosbn:已修复失踪父母。

标签: c++ c++11 lambda data-members


【解决方案1】:

在性能方面,gcc 7.1 -O3 编译后的代码没有区别。两种实现都产生相同的程序集。

测试代码:

int callerFunc(int init)
{
    A st(init);
    return st.j; 
}

编译为:

callerFunc(int):
        test    edi, edi
        jle     .L7
        lea     eax, [rdi-1]
        cmp     eax, 7
        jbe     .L8
        pxor    xmm0, xmm0
        mov     edx, edi
        xor     eax, eax
        movdqa  xmm1, XMMWORD PTR .LC0[rip]
        shr     edx, 2
        movdqa  xmm2, XMMWORD PTR .LC1[rip]
.L5:
        add     eax, 1
        paddd   xmm0, xmm1
        paddd   xmm1, xmm2
        cmp     eax, edx
        jb      .L5
        movdqa  xmm1, xmm0
        mov     edx, edi
        and     edx, -4
        psrldq  xmm1, 8
        paddd   xmm0, xmm1
        movdqa  xmm1, xmm0
        cmp     edi, edx
        psrldq  xmm1, 4
        paddd   xmm0, xmm1
        movd    eax, xmm0
        je      .L10
.L3:
        lea     ecx, [rdx+1]
        add     eax, edx
        cmp     edi, ecx
        jle     .L1
        add     eax, ecx
        lea     ecx, [rdx+2]
        cmp     edi, ecx
        jle     .L1
        add     eax, ecx
        lea     ecx, [rdx+3]
        cmp     edi, ecx
        jle     .L1
        add     eax, ecx
        lea     ecx, [rdx+4]
        cmp     edi, ecx
        jle     .L1
        add     eax, ecx
        lea     ecx, [rdx+5]
        cmp     edi, ecx
        jle     .L1
        add     eax, ecx
        lea     ecx, [rdx+6]
        cmp     edi, ecx
        jle     .L1
        add     eax, ecx
        add     edx, 7
        lea     ecx, [rax+rdx]
        cmp     edi, edx
        cmovg   eax, ecx
        ret
.L7:
        xor     eax, eax
.L1:
        rep ret
.L10:
        rep ret
.L8:
        xor     eax, eax
        xor     edx, edx
        jmp     .L3
.LC0:
        .long   0
        .long   1
        .long   2
        .long   3
.LC1:
        .long   4
        .long   4
        .long   4
        .long   4

【讨论】:

    【解决方案2】:

    我想到了几个不同点:

    1. 无法重载 lambda。因此,任何继承的类都将使用相同的函数。
    2. 默认情况下,lambda 允许您捕获局部变量,如果重命名/重新排序可能会导致错误。如果你明确地传递它们,这可以得到缓解。虽然重新排序对于方法和 lambda 类似物来说是危险的,但默认 lambda 捕获更不稳定,因为您重新排序的变量将被隐式传递给 lambda。

    【讨论】:

    • 没有办法以有意义的方式重载J,因为它是在构造时调用的,并且此时不会连接任何下降的虚拟重载。
    • 而且方法在变量排序方面也有同样的缺陷。
    • @Jarod42 是的,但它仍然比默认捕获风险更小,因为重新排序的变量不一定传递到方法中。
    • @Yakk 好点,我需要稍微解决一下这个问题:)
    • 我只需要通过this。为此,我可以使用[=][&amp;]。两者对数据成员和成员函数的作用是等效的。
    猜你喜欢
    • 2017-02-27
    • 2022-01-07
    • 2011-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-22
    • 2020-03-16
    • 2020-01-06
    相关资源
    最近更新 更多