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
| (function () { })();
(() => { })();
(async () => { })();
|
IIFE的目的是创建局部作用域以避免污染全局命名空间,每一个IIFE中的变量不会在全局作用域下被访问。
在C++中,可以通过匿名lambda实现IIFE。其实如果只是要创建局部作用域,使用{}
将代码块包住就行。但通过匿名lambda实现的IIFE除了创建局部作用域外,还有另外两个功能:常变量复杂初始化;冷/热代码块编译优化。
常变量复杂初始化
常变量必须在声明时初始化。对于常变量简单初始化,可以直接通过表达式完成:
但是对于常变量复杂初始化,只能通过函数完成。初始化代码往往是一次性的,通过使用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; }
|
作为替代,有以下两种方法:
- 用C++20引入的属性
[[unlikely]]
来提示编译器优化代码布局:
1 2 3 4 5
| const bool failed = foo(); if (failed) [[unlikely]] { printf("error!"); return; }
|
- 通过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
| 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);
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