C++ 高阶函数:声明多个函数时的不同结果
C++ higher order functions: different results when declaring multiple functions
我一直在使用高阶函数,当我创建两个实例并将它们分配给变量时,我开始得到不同的结果。
我将问题简化为以下示例:
#include <iostream>
using ColorGetter = int(*)(void);
auto paint(const ColorGetter &f)
{
return [&]() { return f(); };
}
int colorA() { return 10; }
int colorB() { return 20; }
int main()
{
auto painter1 = paint(colorA);
auto painter2 = paint(colorB);
std::cout << "painter 1 : " << painter1() << "\n";
std::cout << "painter 2 : " << painter2() << "\n";
auto p1 = [] () { return colorA(); };
auto p2 = [] () { return colorB(); };
std::cout << "p 1 : " << p1() << "\n";
std::cout << "p 2 : " << p2() << "\n";
}
我的期望是从两个序列中得到 10 个,然后是 20 个。
相反,根据编译器,我得到:
➜ tmp clang++-13 -o out.gcc wrong.cpp&& ./out.gcc
painter 1 : 10
painter 2 : 20
p 1 : 10
p 2 : 20
➜ tmp g++-11 -o out.gcc wrong.cpp && ./out.gcc
painter 1 : 20
painter 2 : 20
p 1 : 10
p 2 : 20
上面的代码有什么根本性的错误吗?
我没有收到编译器警告或 clang-tidy 问题,至少在我当前的设置下是这样。
我们来看这段代码:
auto paint(const ColorGetter &f)
{
return [&]() { return f(); };
}
f
参数仅在函数 paint
的生命周期内存在。您的 lambda 函数通过引用(即 [&]
位)捕获它,因此您的 lambda 捕获引用的变量(引用 f
)在函数结束后不再存在。结果,您返回的对象持有对该函数的悬空引用。您在这里看到未定义的行为,这就是结果因编译器而异的原因。
要解决此问题,请将代码更改为
auto paint(const ColorGetter &f)
{
return [=]() { return f(); };
}
这将导致 lambda 捕获复制存储在 f
中的值(指向相关函数的指针),这将超过 paint
函数。
您没有跟踪生命周期。
auto paint(const ColorGetter &f)
{
return [&]() { return f(); };
}
这会捕获对 f
的引用。当 lambda 超过它创建的范围时,你几乎不应该使用 [&]
。
f
是:
auto painter1 = paint(colorA);
在此行创建的临时指针。它在语句末尾被丢弃。
因此,当您的代码执行 f()
时,它会表现出未定义的行为 -- 您正在跟随悬空引用。
简单的修复包括:
auto paint(const ColorGetter &f)
{
return [=]() { return f(); };
}
我也会去掉 reference-to-pointer:
auto paint(const ColorGetter f)
{
return [=]() { return f(); };
}
当我在做的时候。盲目拿const&
去争论是个坏习惯。就像盲目地按价值看待事物一样。知道你在传递什么。
我一直在使用高阶函数,当我创建两个实例并将它们分配给变量时,我开始得到不同的结果。
我将问题简化为以下示例:
#include <iostream>
using ColorGetter = int(*)(void);
auto paint(const ColorGetter &f)
{
return [&]() { return f(); };
}
int colorA() { return 10; }
int colorB() { return 20; }
int main()
{
auto painter1 = paint(colorA);
auto painter2 = paint(colorB);
std::cout << "painter 1 : " << painter1() << "\n";
std::cout << "painter 2 : " << painter2() << "\n";
auto p1 = [] () { return colorA(); };
auto p2 = [] () { return colorB(); };
std::cout << "p 1 : " << p1() << "\n";
std::cout << "p 2 : " << p2() << "\n";
}
我的期望是从两个序列中得到 10 个,然后是 20 个。 相反,根据编译器,我得到:
➜ tmp clang++-13 -o out.gcc wrong.cpp&& ./out.gcc
painter 1 : 10
painter 2 : 20
p 1 : 10
p 2 : 20
➜ tmp g++-11 -o out.gcc wrong.cpp && ./out.gcc
painter 1 : 20
painter 2 : 20
p 1 : 10
p 2 : 20
上面的代码有什么根本性的错误吗? 我没有收到编译器警告或 clang-tidy 问题,至少在我当前的设置下是这样。
我们来看这段代码:
auto paint(const ColorGetter &f)
{
return [&]() { return f(); };
}
f
参数仅在函数 paint
的生命周期内存在。您的 lambda 函数通过引用(即 [&]
位)捕获它,因此您的 lambda 捕获引用的变量(引用 f
)在函数结束后不再存在。结果,您返回的对象持有对该函数的悬空引用。您在这里看到未定义的行为,这就是结果因编译器而异的原因。
要解决此问题,请将代码更改为
auto paint(const ColorGetter &f)
{
return [=]() { return f(); };
}
这将导致 lambda 捕获复制存储在 f
中的值(指向相关函数的指针),这将超过 paint
函数。
您没有跟踪生命周期。
auto paint(const ColorGetter &f)
{
return [&]() { return f(); };
}
这会捕获对 f
的引用。当 lambda 超过它创建的范围时,你几乎不应该使用 [&]
。
f
是:
auto painter1 = paint(colorA);
在此行创建的临时指针。它在语句末尾被丢弃。
因此,当您的代码执行 f()
时,它会表现出未定义的行为 -- 您正在跟随悬空引用。
简单的修复包括:
auto paint(const ColorGetter &f)
{
return [=]() { return f(); };
}
我也会去掉 reference-to-pointer:
auto paint(const ColorGetter f)
{
return [=]() { return f(); };
}
当我在做的时候。盲目拿const&
去争论是个坏习惯。就像盲目地按价值看待事物一样。知道你在传递什么。