Chapter 03無料公開

状態をもたない関数オブジェクトの operator() をstaticにしよう

Tetsuro Matsumura
Tetsuro Matsumura
2024.11.01に更新

関数オブジェクトは関数ポインタに比べてインライン化しやすく、コンパイラの最適化を促進する利点があります。しかし、関数オブジェクトの 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 ポインタの受け渡しがなくなり、生成されるコードがより簡潔になっていることがわかります。

このチャプターの参考リンク