C++でのローカル関数の実現法C++でのローカル関数の実現法このページをスマートフォンなどでご覧になる場合は、画面を横長にする方が読みやすくなる事があります。リスト2の無名名前空間の例を追加。リスト12の構造体のメンバ変数を使ってauto変数をローカル関数と共有する例を追加。このサイトでは、普段はハードウェアの話がメインになるのですが、今回は純粋にソフトウェアの話です。C++では、関数を宣言すると、そのスコープはプロジェクト全体に広がり、どこからでも見えてしまいます。一方で、特定の関数からしか呼ばれない関数を、他の関数から隠蔽したい事は良くあります。この記事では、特定の関数からしか見えない、ローカルスコープの関数(以後、ローカル関数と呼ぶ)をC++で疑似的に実現する方法を説明します。1ページ1.C++の関数のスコープ本題に入る前に、C++で関数を宣言する場合に、関数がどれだけの広さのスコープを持つかをまず確認しておきましょう。ここでいう関数は、クラスのメンバ関数ではなく、どのクラスにも、どの名前空間にも所属しない関数の事を指します。1-1.普通の関数のスコープ例えば、リスト1に示す様な関数globalがあるとします。2*n;}この関数のスコープは、同一プロジェクト全体に広がります。global関数を定義したファイルからも呼び出す事ができますが、同一プロジェクトの他のファイルからかも呼び出す事ができます。リスト1の様な形で関数を宣言すると、複数のソースリストからなるプロジェクトの開発をしている場合、各ファイルで同じ名前の関数ができないように、関数の命名に気を付けないといけません。うっかり同じ名前の関数を同一プロジェクト内で複数作ると、リンク時にエラーになります。これは、複数の人が共同して開発を行う場合に、特に深刻な問題になります。1-2.無名名前空間内の関数、static宣言された関数のスコープ他のファイルから関数を隠蔽したい場合は、リスト2の様に、無名名前空間を使うか、リスト3の様にstatic修飾子を付けて関数を定義します。注:関数をstatic宣言してスコープをファイル内に限定する手法は、C言語との互換性のために用意されている機能で、C++では利用を推奨されていません。2*n;}これらの様にしておけば、同一プロジェクトの別のファイルに、同じfile_localという名前の関数があっても、それとは別の関数としてリンクされます。これらの例の様に、同一ファイル内にまで関数のスコープを狭くする事ができますが、通常は、それ以上スコープを狭める事はできません。無名でない名前空間を利用して、さらにスコープを狭める方法もありますが、関数内で利用するローカル関数の宣言に使うには、記述が美しくないので、ここでは名前空間は扱わない事にします。2.ローカルクラスを使ったローカル関数の実現法C++では、関数内で関数を定義する事はできないものの、関数内でクラスを定義するローカルクラスは許されています。関数内でクラスを定義すると、そのクラスは定義した関数内からしか見えません。ローカルクラスもクラスの一種ですから、メンバ関数を持つ事ができます。ローカルクラスのメンバ関数は、クラスを定義した関数内にスコープが限定されますから、ローカルクラスのメンバ関数は、実質上のローカル関数(関数内関数)になります。1つのメンバ関数を定義しただけの構造体(C++では構造体は、メンバがデフォルトでpublicになる、一種のクラス)を使ってローカル関数を実現した例が、第四報:怪しい関数内関数に紹介されていました。リンク先の記事では、マクロを使って記述を見やすくしようとしていますが、マクロを使わずに同様の方法でローカル関数を定義すると、リスト4の様になります。3と5の和(8)を表示する}LocalFuncという構造体が、ローカル関数を定義するための構造体です。この構造体の内部でaddという、2つの引数の和を返すメンバ関数を定義しています。addは静的メンバ関数ですから、LocalFunc::add(3,5)の様に、前にLocalFunc::を付ければ呼び出せます。LocalFunc構造体は、main関数内のローカル構造体ですから、main関数内からしか見えません。リスト4にはmain以外の関数がありませんが、もし他の関数があったとしても、そこからはLocalFunc構造体が見えず、よってLocalFunc::add関数も呼べません。1.6.6に文字コード関係のバグ?という記事内のリスト9のConvStr関数でも利用しています。リスト5の様に構造体内で複数のメンバ関数を定義すれば、1つの関数内で複数のローカル関数が使えます。3と5の積(15)を表示する}3.ローカルな関数オブジェクトを使って記述をきれいにする先ほど紹介したLocalFuncという構造体を利用してローカル関数を実現した例では、関数のスコープを制限するという意味では、うまくローカル関数を実現できていたのですが、関数を呼び出す際に、いちいちLocalFunc::を付けないといけないのが面倒くさいです。意味のない構造体に、LocalFuncという名前をつけなければならないこと自体が美しくありません。先日関数オブジェクトの勉強をしていたところ、関数オブジェクトを使う事で、ローカル関数の呼び出しの記述が綺麗になる事に気が付きました。関数オブジェクトを使って実際にローカル関数addを定義した例をリスト6に示します。cout<<add(3,5)<<endl;}ローカル構造体の()演算子をオーバーロードして、ローカルな関数オブジェクトを作っています。使い捨ての構造体ですから、構造体自体には名前を付けていません。構造体の定義と同時に、addという変数を宣言しています。ローカル構造体は()演算子をオーバーロードしていますから、add(3,5)という具合に変数名の後に(3,5)を付けると、メンバ関数が、3と5の引数で呼ばれます。この様に、ローカルな関数オブジェクトを使うと、普通の関数と同じ形式でローカル関数が呼べますし、ローカル関数の定義のための構造体に名前を付ける必要もありません。関数オブジェクトを利用しますから、関数オブジェクトに慣れていない人は、関数の定義の部分が、ちょっと奇異に感じるかもしれません。ローカル関数の定義をする際に、関数名が一番最後に来るのも、ソースの他の部分の書き方と違ってきます。4.ローカルな関数オブジェクトを使って、再帰呼び出しのあるローカル関数を実現する再帰呼び出しのあるローカル関数は、thisポインタを使えば記述できます。メンバ関数の名前はoperetor()ですから、それを使って記述する方法もあります。ローカルな関数オブジェクトを使って、再帰呼び出しのあるローカル関数を記述した例をリスト7に示します。4の階乗の24を表示}広告5.ローカル関数内のローカル関数C++では、クラス内で、さらに別のクラスを宣言できます。リスト8は、クラス内でクラスを宣言(厳密には構造体内で構造体を宣言)した例です。すれば内側の構造体のメンバ関数を呼ぶ事もできる}AddSqr構造体の中で、別の構造体Sqrを定義しているのが分かると思います。main関数からはAddSqr::func関数を呼べますが、Sqr::func関数は呼べません。注:ただし、AddSqr::Sqr::funcと記述すれば、内側の構造体のメンバ関数を呼び出すこともできますので、外部から完全に隠蔽できているわけではありません。このクラス内クラス(構造体内構造体)の考え方と、関数内のローカルクラスの考え方と、関数オブジェクトの考え方をミックスすれば、リスト9の様に、ローカル関数の中に、さらにローカルな関数を定義する事ができます。リスト9の場合は、変数Sqrのスコープが、外側の構造体の内部に制限されているため、Sqr関数は、AddSqr関数の外部から完全に隠蔽されています。3^2+4^2の計算結果(25)を表示}6.ローカル関数外の変数へのアクセスローカル関数で、ローカル関数の外の変数へアクセスできるのでしょうか?グローバル変数にアクセスする場合と、関数内のローカル変数へアクセスする場合とに分けて考えます。6-1.グローバル変数へのアクセスは可能グローバル変数はクラス内からでも参照できるため、リスト10に示す様に、ローカル関数は、グローバル変数には問題なくアクセスできます。次に、ローカル関数の外側の関数のローカル変数へのアクセスについて考えます。リスト11のmain関数には、LocalAutoとLocalStaticの2つのローカル変数があります。LocalAuto変数の方はstatic修飾子が付いていないので、auto変数(自動変数)になります。一方で、LocalStatic変数の方は、static変数になります。LocalAuto変数も、LocalStatic変数も、ローカル関数のスコープには入っているのですが、C++ではローカルクラスからauto変数にはアクセスできないので、ローカル関数からLocalAuto変数にアクセスすると、コンパイル時にエラーになります。一方で、staticなローカル変数(LocalStatic変数)にはアクセスできます。456を表示}どうしても、関数とそのローカル関数とでauto変数を共有したい場合は、ローカル関数を定義するための構造体のメンバ変数を使えば、リスト12の様に(事実上の)auto変数を共有できます。記述が汚いのであまりお勧めできる方法ではありません。関数とそのローカル関数で共有したいauto変数は、ローカル関数を定義するための構造体のメンバにする。関数オブジェクトのインスタンス(ReadLocalAuto)がauto変数なので、その(非static)メンバ変数はauto変数になる。必要ならば、確保した変数への参照を宣言しておくと、普通のauto変数っぽくアクセスできる。これ以降は、LocalAutoを普通のauto変数として、ReadLocalAutoをLocalAutoを読むためのローカル関数として扱える。実際にはReadLocalAuto.LocalAutoへの代入。}7.その他の制限このページで提案した手法では、ローカル関数内にさらにローカル関数がある場合に、内側のローカル関数から外側のローカル関数を呼ぶ事(一種の再帰呼び出し)はできません。Pascalでは、内側のローカル関数から外側のローカル関数を呼ぶ事が可能です。グローバル手続きtestの定義。test;end.また、このページの提案手法では、ローカル関数の中にさらにローカル関数がある場合、内側のローカル関数が外側のローカル関数のローカル変数を使う事はできません。この場合もPascalなら可能です。