このチャプターの目次
関数オブジェクトは関数ポインタに比べてインライン化しやすく、コンパイラの最適化を促進する利点があります。しかし、関数オブジェクトの operator()
はメンバ関数であるため、インライン化されなかった場合には、this
ポインタを受け渡すという小さなコストが発生します。
C++23より前は、状態を持たない関数オブジェクトについても、operator()
は非静的メンバ関数として定義する必要がありました。
struct CompObj {
bool operator()(int) const;
};
int F(const std::vector<int>& v) {
return std::ranges::count_if(v, CompObj{});
}
C++23 での改善
C++23 では、operator()
が this
を使用しない(状態を持たない)場合、static
として宣言できるようになりました。これにより、コンパイラは this
ポインタの受け渡しを考慮する必要がなくなり、より最適化されたコードを実現できます。
次のコードは、C++23 で static operator()
を使用した例です。
struct CompObj {
static bool operator()(int);
};
int F(const std::vector<int>& v) {
return std::ranges::count_if(v, CompObj{});
}
生成されるコードの比較
static operator()
を使用することで、生成されるコードがどのように変化するのか、記事中のコードを使ってコンパイルした結果を見てみましょう。
static operator() を使用しない場合 (GCC, -O2):
F(std::vector<int, std::allocator<int> > const&):
push r13
push r12
push rbp
push rbx
sub rsp, 24
mov r12, QWORD PTR [rdi+8]
mov rbx, QWORD PTR [rdi]
cmp r12, rbx
je .L4
xor ebp, ebp
.L3:
mov esi, DWORD PTR [rbx]
lea rdi, [rsp+15]
add rbx, 4
call CompObj::operator()(int) const
movzx eax, al
add rbp, rax
cmp r12, rbx
jne .L3
add rsp, 24
mov eax, ebp
pop rbx
pop rbp
pop r12
pop r13
ret
.L4:
add rsp, 24
xor eax, eax
pop rbx
pop rbp
pop r12
pop r13
ret
C++23 で static operator() を使用する場合 (GCC, -O2):
F(std::vector<int, std::allocator<int> > const&):
push r12
push rbp
push rbx
mov r12, QWORD PTR [rdi+8]
mov rbx, QWORD PTR [rdi]
cmp r12, rbx
je .L4
xor ebp, ebp
.L3:
mov edi, DWORD PTR [rbx]
add rbx, 4
call CompObj::operator()(int)
movzx eax, al
add rbp, rax
cmp r12, rbx
jne .L3
mov eax, ebp
pop rbx
pop rbp
pop r12
ret
.L4:
pop rbx
xor eax, eax
pop rbp
pop r12
ret
このように、C++23 で static operator()
を使用することで、this
ポインタの受け渡しがなくなり、生成されるコードがより簡潔になっていることがわかります。