C++ IIFE

immediately invoked function expression

IIFE(immediately invoked function expression)即立即调用函数表达式,这个术语源自于JavaScript,由Ben Alman他的博客中提出,指在定义匿名函数后立即执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 标准 IIFE
(function () {
// 语句……
})();

// 箭头函数变体
(() => {
// 语句……
})();

// 异步 IIFE
(async () => {
// 语句……
})();

IIFE的目的是创建局部作用域以避免污染全局命名空间,每一个IIFE中的变量不会在全局作用域下被访问。

在C++中,可以通过匿名lambda实现IIFE。其实如果只是要创建局部作用域,使用{}将代码块包住就行。但通过匿名lambda实现的IIFE除了创建局部作用域外,还有另外两个功能:常变量复杂初始化;冷/热代码块编译优化。

常变量复杂初始化

常变量必须在声明时初始化。对于常变量简单初始化,可以直接通过表达式完成:

1
const int a = x * y;

但是对于常变量复杂初始化,只能通过函数完成。初始化代码往往是一次性的,通过使用IIFE,可以避免增加不必要的函数或类的成员函数:

1
2
3
4
5
6
7
8
const int a = [&] {
switch (mode) {
case 0: return x * y;
case 1: return x / y;
case 2: return x % y;
default: return -1;
}
}();

使用C++17引入的std::invoke(),可以实现更好的可读性:

1
2
3
4
5
6
7
8
const int a = std::invoke([&] { 
switch (mode) {
case 0: return x * y;
case 1: return x / y;
case 2: return x % y;
default: return -1;
}
});

冷/热代码块编译优化

当使用异常进行错误处理时,GCC会假定错误分支不太可能发生,并将错误处理代码放到一个单独的冷区(cold section),使其与正常代码分离。这样可以减少对指令缓存的压力,并提升代码性能。

但如果使用返回值进行错误处理,GCC不会自动将错误处理代码放到冷区:

1
2
3
4
5
const bool failed = foo();
if (failed) {
printf("error!");
return;
}

作为替代,有以下两种方法:

  1. 用C++20引入的属性[[unlikely]]来提示编译器优化代码布局:
1
2
3
4
5
const bool failed = foo();
if (failed) [[unlikely]] {
printf("error!");
return;
}
  1. 通过IIFE使用GCC特有的函数属性noinline和cold:
1
2
3
4
5
6
7
const bool failed = foo();
if (failed) {
[&]() __attribute__((noinline,cold)) {
printf("error!");
}();
return;
}

可以在https://gcc.godbolt.org/网站观察以下代码的汇编代码的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <cstdio>
#include <stdexcept>

extern bool foo();

void badCaseOnHotPath() {
const bool failed = foo();
if (failed) {
printf("error!");
return;
}
}

void badCaseOutOfLine() {
const bool failed = foo();
if (failed) {
[&]() __attribute__((noinline,cold)) {
printf("error!");
}();
return;
}
}

void badCaseUsingExcpetion() {
const bool failed = foo();
if (failed) {
throw std::runtime_error("error!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
.LC0:
.string "error!"
badCaseOutOfLine()::'lambda'()::operator()() const (.constprop.0):
mov edi, OFFSET FLAT:.LC0
xor eax, eax
jmp printf
badCaseOnHotPath():
sub rsp, 8
call foo()
test al, al
jne .L6
add rsp, 8
ret
.L6:
mov edi, OFFSET FLAT:.LC0
xor eax, eax
add rsp, 8
jmp printf
badCaseOutOfLine():
sub rsp, 8
call foo()
test al, al
jne .L12
.L7:
add rsp, 8
ret
badCaseOutOfLine() (.cold):
.L12:
call badCaseOutOfLine()::'lambda'()::operator()() const (.constprop.0)
jmp .L7
badCaseUsingExcpetion():
push r12
push rbp
sub rsp, 8
call foo()
test al, al
jne .L18
add rsp, 8
pop rbp
pop r12
ret
badCaseUsingExcpetion() (.cold):
.L18:
mov edi, 16
call __cxa_allocate_exception
mov esi, OFFSET FLAT:.LC0
mov rdi, rax
mov rbp, rax
call std::runtime_error::runtime_error(char const*) [complete object constructor]
mov edx, OFFSET FLAT:std::runtime_error::~runtime_error() [complete object destructor]
mov esi, OFFSET FLAT:typeinfo for std::runtime_error
mov rdi, rbp
call __cxa_throw
mov r12, rax
mov rdi, rbp
call __cxa_free_exception
mov rdi, r12
call _Unwind_Resume

仅创建局部作用域

这种写法作用和{}一样。

UnrealEngine/Engine/Source/Editor/BlueprintGraph/Private/CallFunctionHandler.cpp at 5.1 · EpicGames/UnrealEngine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Ensure all literal struct terms can be imported if its empty
if ( (*Term)->Name.IsEmpty() )
{
(*Term)->Name = TEXT("()");
}

int32 StructSize = Struct->GetStructureSize();
[this, StructSize, StructProperty, Node, Term, &bMatchedAllParams]()
{
uint8* StructData = (uint8*)FMemory_Alloca(StructSize);
StructProperty->InitializeValue(StructData);

// Import the literal text to a dummy struct to verify it's well-formed
FImportTextErrorContext ErrorPipe(CompilerContext.MessageLog, Node);
StructProperty->ImportText_Direct(*((*Term)->Name), StructData, nullptr, 0, &ErrorPipe);
if(ErrorPipe.NumErrors > 0)
{
bMatchedAllParams = false;
}
}();

参考

IIFE for Complex Initialization - C++ Stories

Uses of Immediately Invoked Function Expressions (IIFE) in C++ | Erik Rigtorp


C++ IIFE
https://tech.reddish.fun/Article/CPP-IIFE/
作者
bit704
发布于
2025年4月6日
许可协议