プログラミング言語の機能の1つで、ある関数が、その関数を定義したときのスコープを保持し続けることにより、その関数の外側のスコープにある変数にアクセスできる性質のことです。また、これを実現した関数オブジェクトのことです。
通常、関数の本体のコードからアクセスできる変数は、その関数内で定義したローカル変数、引数、グローバル変数、メンバ関数であればメンバ変数といったものに限られます。そのため、関数内で定義された関数(ローカル関数)において、内側の関数からは、外側の関数で定義されている変数にアクセスできません。以下は架空のプログラミング言語の例です。
int outer(int v)
{
int x = 100;
// 関数内で定義された関数(ローカル関数)
int inner(int z)
{
return x + v + z; // x や v にはアクセスできない
}
return inner(10);
}
クロージャがどのような文法や方法で実現されるかはプログラミング言語によって異なりますが、クロージャがあれば、inner関数が定義されたときのスコープを保持して、x や v へのアクセスが可能になります。以下のコードも架空のプログラミング言語の例です。
f outer(int v)
{
int x = 100;
// 関数内で定義された関数(ローカル関数)
int inner(int z)
{
return x + v + z;
}
return inner; // inner関数を返して、クロージャを生成する
}
int other()
{
var f = outer(123); // f はクロージャ
var r1 = f(10); // inner関数が呼び出される。100 + 123 + 10 から 233 が得られる
var r2 = f(20); // inner関数が呼び出される。100 + 123 + 20 から 243 が得られる
}
outer関数から inner関数を返しており、呼び出し元がこれを受け取ることでクロージャ(関数オブジェクト)である f を生成しています。すでに outer関数から離れていますが、f(10)
のような呼び出しが可能であり、inner関数が定義されたスコープが保持されているため、outer関数内にあった変数x や引数y にもアクセスできます。このようなことが可能になるのは、クロージャが関数とスコープをセットにした状態を保持しているからです。そして、この例が示すように、関数をほかの関数に渡したり返したりすることが可能になっています(高階関数)。
C++ の場合、関数オブジェクトを用いて同様のことが実現されます。特に、C++11 で追加されたラムダ式を用いることで、さきほどのサンプルプログラムに近しいことが可能になります。
auto outer(int v)
{
int x = 100;
auto inner = [=](int z){ return x + v + z; };
return inner;
}
int main()
{
auto f = outer(123);
int r1 = f(10);
int r2 = f(20);
}
ラムダ式 [=](int z){ return x + v + z; }
のうち、[=]
の部分により、現在のスコープ内にある変数のコピーが保持されます(コピーキャプチャ)。C++ のラムダ式は関数オブジェクトを生成する構文ですが、上記のラムダ式からは次の関数オブジェクトが生成されています。
class ???? {
int x; // outer関数の変数x をコピー
int v; // outer関数の引数v をコピー
public:
inline int operator()(int z) const
{
return x + v + z;
}
};
このため、現在のスコープから離れても、本体のコードの中で x や v にアクセスできます。また、C++ では、こうして生成されたクラスをクロージャ型と呼び、この型のオブジェクトをクロージャオブジェクトと呼びます。
C++ のラムダ式についての詳細な解説は、新C++編「関数ポインタとラムダ式」にあります。
Programming Place Plus のトップページへ
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
![]() |
管理者情報 | プライバシーポリシー |