ローカル関数宣言の使用はありますか?
-
06-07-2019 - |
質問
私のようなほとんどのC ++プログラマーは、ある時点で次の間違いを犯しました。
class C { /*...*/ };
int main() {
C c(); // declares a function c taking no arguments returning a C,
// not, as intended by most, an object c of type C initialized
// using the default constructor.
c.foo(); // compiler complains here.
//...
}
エラーを知ったらすぐに明らかになりますが、この種のローカル関数宣言には、それを行うことができる以外に賢明な使用があるかどうか疑問に思っていました-特に define 同じブロック内のそのようなローカル関数。他の場所で定義する必要があります。
Javaスタイルのローカルクラスは、特に匿名ソートなど、頻繁に使用する傾向のある非常に優れた機能だと思います。ローカルC ++クラス(インライン定義のメンバー関数を持つことができます)でさえ、いくつかの用途があります。しかし、定義のないこのローカル関数宣言は、私にとって非常に厄介なようです。それは単なるCレガシーですか、それとも私が気付いていないより深いユースケースがありますか?
非信者向けの 編集: C c()
は関数ポインター宣言ではありません 。
このプログラム
int main()
{
void g();
cout << "Hello ";
g();
return 0;
}
void g()
{
cout << "world." << endl;
}
出力 Hello world。
このプログラム
void fun()
{
cout << "world." << endl;
}
int main()
{
void g();
g = fun;
cout << "Hello ";
g();
return 0;
}
コンパイルしません。 gccからの不満:
error: cannot convert 'void ()()' to 'void ()()' in assignment
comeau:
error: expression must be a modifiable lvalue
解決
引数として他の関数に渡したいときに、Cでローカル関数宣言が必要でした。私はいつも他の言語でこれをしています。その理由は、データ構造の実装をカプセル化するためです。
E.g。私はいくつかのデータ構造を定義します、例えばツリーまたはグラフ。内部実装の詳細を公開したくありません。私はそれを変えたり変えたりしたいかもしれないからです。そのため、その要素のアクセサー関数とミューテーター関数、および要素を反復処理するトラバーサル関数を公開します。トラバーサル関数には、引数として要素を操作する関数があります。トラバーサル関数のタスクは、各要素で引数関数を実行し、結果を何らかの方法で集約することです。トラバーサル関数を呼び出すとき、引数関数は通常、ローカル状態に依存する特殊な操作であるため、ローカル関数として定義する必要があります。ローカル状態を含む変数、グローバルにするための関数、またはそれらを保持するために特別に作成された内部クラスに関数をプッシュすることは、見苦しいです。しかし、これは私がC ++ではなくCとJavaで抱えている問題です。
他のヒント
考えられる唯一の用途は、関数宣言の範囲を縮小することです:
int main()
{
void doSomething();
doSomething();
return 0;
}
void otherFunc()
{
doSomething(); // ERROR, doSomething() not in scope
}
void doSomething()
{
...
}
もちろん、これにははるかに優れたソリューションがあります。関数を非表示にする必要がある場合、非表示にする関数を呼び出す必要がある関数がすべて同じモジュール内にあるように、関数を個別のモジュールに移動してコードを実際に再構築する必要があります。次に、その関数を静的に宣言する(Cの方法)か、匿名の名前空間内に置く(C ++の方法)ことにより、その関数をモジュールローカルにすることができます。
これは、前方宣言のプロトタイプです。おそらく、多くのローカル関数、または定義の順序に反して互いに呼び出すローカル関数がある場合、それが必要になるかもしれません。
私が見ることができる唯一の正しい使用法は、コンパイルユニット内の1つの関数のみが別のコンパイルユニットで定義された関数について知ることを許可することです。それはいくぶん合理的な使用法だと思いますが、それは私が考えることができる唯一のものであり、それは行き過ぎだと思います。
この回答の3番目のスニペットで述べたように、スコープシャドウイングに役立ちます。
#include <stdio.h>
void c(); // Prototype of a function named ``c''
int main() {
c(); // call the function
{ // additional scoppe needed for C, not for C++
int c = 0; // shadow the c function with a c integer variable
c++;
{
void c(); // hide the c integer variable back with the c function
c();
}
++c;
} //!
return 0;
}
void c() {
printf("Called\n");
}
この使用は頻繁に役立つとは思われず、適切に設計されたプログラムでは発生しない可能性があります。
私はまだ、これがその機能の最も可能性の高い理由だと思います。最近変数を定義すると、関数は正しく聞こえないだけです。
私は時々、変数を最初に使用する直前に宣言することが推奨されているのと同じ理由で(以前ではなく)、読みやすさを改善するためにそれを行います。 (はい、変数については、変数が最初に使用されると思われる前に変数が使用されているかどうかを確認する必要がないため、より重要であることを認識しています)。プロトタイプを(特に c()
よりも複雑な場合)関数呼び出しの近くに置くと、可読性が向上します。特殊なケース C c()
が人間にとって誤解を招くという事実は残念ですが、関数をローカルで宣言するという一般的な考え方にはメリットがあります。
もちろん、これは既にスコープ内にある関数名にも当てはまります。使用する前にすべての関数を再宣言してみませんか?それに対する私の答えは次のとおりです。混乱が多すぎると、可読性が低下します(また、保守性---関数のシグネチャが変更された場合)。そのため、私はそれからルールを作成しませんが、ローカルで関数を宣言すると便利な場合があります。
パラメータを受け取らない関数の宣言と、デフォルトのデストラクタを使用したクラスのインスタンス化を区別する場合は、括弧をスキップします。
class C
{
public:
void foo() {}
};
int main()
{
// declare function d, taking no arguments, returning fresh C
C d();
// instantiate class (leave parenthesis out)
C c;
c.foo();
// call declared function
C goo = d();
return 0;
}
C d()
{
C c;
// ..
return c;
}