C++で一般に「無名関数」と呼ばれるものは、通常 ラムダ式(Lambda Expression) を指します。
ラムダ式は 名前を付けずにその場で関数のように呼び出せる処理を定義できる構文です。
ただし、厳密な意味では C++ のラムダは「関数そのもの」ではありません。
実際には、コンパイラが クロージャ型(closure type)と呼ばれる無名のクラス型を生成し、そのオブジェクトを作る仕組みになっています。
このオブジェクトは 関数呼び出し演算子(operator())を持つ関数オブジェクトとして振る舞うため、見た目は関数のように扱うことができます。
そのため、C++では次のように理解すると正確です。
- ラムダ式は関数ではなく 関数のように呼び出せるオブジェクトを生成する式
- そのオブジェクトは クロージャオブジェクトと呼ばれる
ラムダ式が導入された背景
ラムダ式は C++11 で導入されました。
導入の主な目的は次のような問題を解決することです。
従来のC++では、短い処理をアルゴリズム関数に渡したい場合でも
- 関数を別に定義する
- 関数オブジェクト(functor)を作る
必要があり、コードが冗長になりがちでした。
ラムダ式を使うと、その場で短い処理を定義できるためコードが簡潔になります。
ラムダ式の特徴
C++のラムダ式にはいくつかの重要な特徴があります。
主なものは次の通りです。
名前を持たない
ラムダ式は通常の関数と違い、関数名を持ちません。
必要であれば変数に代入して使うこともできますが、本質的には 匿名の処理として定義されます。
外部変数を取り込める(キャプチャ)
ラムダ式の大きな特徴は 外側のスコープにある変数を取り込めることです。
この仕組みを キャプチャ(capture) と呼びます。
キャプチャには大きく分けて次の方法があります。
値キャプチャ
外部変数の値をコピーしてラムダ内部に保持します。
ラムダの内部ではそのコピーが使用されます。
参照キャプチャ
外部変数を参照として取り込みます。
そのためラムダ内部で値を変更すると、外側の変数も変更されます。
使用した変数のみがキャプチャ対象になる
すべての外部変数が自動的にキャプチャされるわけではありません。
ラムダ内部で 実際に使用された外側の自動変数のみが対象になります。
また、グローバル変数や static 変数などは通常の意味でキャプチャされるわけではなく、単に参照される形になります。
オブジェクトの this をキャプチャできる
クラスのメンバ関数内では、ラムダ式は 現在のオブジェクトを指す this ポインタをキャプチャできます。
重要なのは、ここでキャプチャされるのは オブジェクトそのものではなくポインタである点です。
そのため
- ラムダがオブジェクトより長く生きる
- オブジェクトが破棄された後にラムダが実行される
といった状況では、ダングリングポインタの問題が起こる可能性があります。
なお、C++17では オブジェクト自体をコピーしてキャプチャする仕組みも追加されています。
キャプチャした値は通常変更できない
ラムダの呼び出し演算子は デフォルトで const です。
そのため、値キャプチャした変数はラムダ内部で変更できません。
これを変更できるようにするには、ラムダを mutable として宣言します。
ただし、この場合でも変更されるのは キャプチャされたコピーであり、外側の変数ではありません。
ラムダ式の型
ラムダ式には明示的な型名がありません。
コンパイラが 一意の無名クラス型を生成します。
この型は
- クロージャ型
- closure type
などと呼ばれます。
そのためラムダ式の値は通常、型推論(autoなど)を使って扱われます。
関数ポインタとの関係
重要な特徴として、
キャプチャを持たないラムダは関数ポインタへ変換できます。
一方で、キャプチャを持つラムダは通常は関数ポインタへ変換できません。
これは、キャプチャ付きラムダが 状態を持つオブジェクトだからです。
STLアルゴリズムとラムダ
ラムダ式は特に STLアルゴリズムと非常に相性が良い機能です。
代表的な用途は次のようなものです。
- 要素に対する処理
- 条件検索
- カスタムソート
- フィルタ処理
ラムダ式を使うことで、小さな処理をその場に直接書けるため、コードの可読性が向上します。
非同期処理・コールバックでの利用
ラムダ式は以下のような場面でもよく利用されます。
コールバック関数
イベント処理
GUIプログラム
非同期処理
スレッド処理
並列処理ライブラリ
非同期タスク
このような用途では、短い処理を関数として渡す必要があるため、ラムダ式が非常に便利です。
ラムダ式のメリット
ラムダ式を使うメリットは主に次の点です。
コードが簡潔になる
短い処理を別の関数として定義する必要がなくなります。
局所性が高い
処理を使う場所の近くに書けるため、コードの理解が容易になります。
状態を持てる
キャプチャにより、外部の変数を安全に取り込めます。
ラムダ式の注意点
ラムダ式は便利ですが、いくつか注意点もあります。
キャプチャの使いすぎ
特に参照キャプチャを多用すると、予期しない副作用が起こることがあります。
ラムダが長くなると読みにくい
処理が複雑になる場合は通常の関数に分離した方がよい場合があります。
オブジェクト寿命の問題
特に this キャプチャでは、オブジェクトの寿命に注意が必要です。
C++のラムダ式の進化
ラムダ式はC++のバージョンごとに機能が拡張されています。
主な進化は次の通りです。
C++11
ラムダ式の導入
C++14
ジェネリックラムダ
(引数の型を自動推論)
C++17
オブジェクトコピーキャプチャ
C++20
テンプレートパラメータを持つラムダ
まとめ
C++の無名関数は、一般的に ラムダ式を指します。
ラムダ式は
- 名前を持たない処理をその場で定義できる
- 外部変数をキャプチャできる
- STLアルゴリズムや非同期処理と相性が良い
という特徴を持つ、モダンC++の重要な機能です。
厳密にはラムダ式は関数ではなく、関数呼び出し演算子を持つ無名のクロージャオブジェクトを生成する構文です。
この仕組みによって、C++では柔軟で表現力の高いプログラミングが可能になっています。
以上、C++の無名関数についてでした。
最後までお読みいただき、ありがとうございました。
