別の関数内で関数を定義できないのはなぜですか?


80

これはラムダ関数の質問ではありません。ラムダを変数に割り当てることができることを私は知っています。

コード内で関数を宣言することはできるが、定義することはできないというのはどういう意味ですか?

例えば:

#include <iostream>

int main()
{
    // This is illegal
    // int one(int bar) { return 13 + bar; }

    // This is legal, but why would I want this?
    int two(int bar);

    // This gets the job done but man it's complicated
    class three{
        int m_iBar;
    public:
        three(int bar):m_iBar(13 + bar){}
        operator int(){return m_iBar;}
    }; 

    std::cout << three(42) << '\n';
    return 0;
}

だから私が知りたいのは、C ++がなぜtwo役に立たないように見え、threeはるかに複雑に見えるが許可しないのoneかということです。

編集:

回答から、コード内宣言は名前空間の汚染を防ぐことができるようですが、私が聞きたかったのは、関数を宣言する機能は許可されているが、関数を定義する機能は許可されていない理由です。


3
1つ目はone関数定義で、他の2つは宣言です。
一部のプログラマーは2015

9
用語の言い方が間違っていると思います。「コード内で関数を定義宣言できるようにすることの意味は何ですか?」と質問したいと思います。そして、私たちがそれに取り組んでいる間、あなたはおそらく「関数の内部」を意味します。それはすべて「コード」です。
ピーター-モニカを復活させる2015年

14
言語に癖や矛盾がある理由を尋ねるなら、それは数十年にわたって、さまざまな理由でさまざまな時期に発明された言語から、さまざまなアイデアを持つ多くの人々の仕事を通じて進化したからです。なぜこの特定の癖があるのか​​と尋ねるなら、ローカル関数の定義が標準化するのに十分有用だと誰も(これまでのところ)考えていなかったからです。
マイクシーモア

4
@MikeSeymourはそれを正しく持っています。Cは、たとえばPascalほど構造化されておらず、常に最上位の関数定義のみが許可されています。したがって、その理由は歴史的であり、それに加えてそれを変更する必要がないことです。関数宣言が可能であるということは、一般的にスコープ宣言が可能であるという結果にすぎません。関数に対してそれを禁止することは、追加のルールを意味するでしょう。
ピーター-モニカを復活させる2015年

3
@JonathanMee:おそらく、一般に宣言はブロックで許可されており、関数宣言を特に禁止する特別な理由はありません。特別な場合なしで宣言を許可する方が簡単です。しかし、「なぜ」は実際には答えられる質問ではありません。それが進化した方法であるため、言語はそれが何であるかです。
マイクシーモア

回答:


41

なぜone許可されないのかは明らかではありません。入れ子関数はずっと前にN0295で提案されました。

ネストされた関数のC ++への導入について説明します。入れ子関数は十分に理解されており、それらの導入には、コンパイラーベンダー、プログラマー、または委員会のいずれかによる労力はほとんど必要ありません。入れ子関数には大きな利点があります[...]

明らかに、この提案は却下されましたが、オンラインで議事録を利用でき1993ないため、この却下の理由について考えられる情報源がありません。

実際、この提案は、可能な代替手段としてC ++のLambda式とクロージャーに記載されています。

ある記事[Bre88]とC ++委員会への提案N0295 [SH93]は、入れ子関数をC ++に追加することを提案しています。ネストされた関数はラムダ式に似ていますが、関数本体内のステートメントとして定義されており、その関数がアクティブでない限り、結果のクロージャは使用できません。これらの提案には、ラムダ式ごとに新しい型を追加することも含まれていませんが、代わりに、特別な種類の関数ポインターがそれらを参照できるようにするなど、通常の関数のように実装しています。これらの提案は両方とも、C ++へのテンプレートの追加よりも前のものであるため、一般的なアルゴリズムと組み合わせた入れ子関数の使用については言及していません。また、これらの提案にはローカル変数をクロージャにコピーする方法がないため、それらが生成する入れ子関数は、囲んでいる関数の外では完全に使用できません。

ラムダがあることを考えると、ネストされた関数が表示される可能性は低いです。これは、ペーパーで概説されているように、それらは同じ問題の代替であり、ネストされた関数にはラムダに関連するいくつかの制限があるためです。

あなたの質問のこの部分に関して:

// This is legal, but why would I want this?
int two(int bar);

これが必要な関数を呼び出すのに便利な方法である場合があります。ドラフトC ++標準セクション3.4.1 [basic.lookup.unqual]は、1つの興味深い例を示しています。

namespace NS {
    class T { };
    void f(T);
    void g(T, int);
}

NS::T parm;
void g(NS::T, float);

int main() {
    f(parm); // OK: calls NS::f
    extern void g(NS::T, float);
    g(parm, 1); // OK: calls g(NS::T, float)
}

1
あなたが与える3.4.1の例に関する質問:mainの呼び出し元::g(parm, 1)は、グローバル名前空間の関数を呼び出すために単に書き込むことができませんでしたか?またはg(parm, 1.0f);、目的のより良い一致をもたらすはずの呼び出しg
ピーター-モニカを復活させる2015年

@PeterSchneiderそこで強すぎる発言をしたので、調整しました。
Shafik Yaghmour 2015

1
ここにコメントを追加したいと思います。この回答は、コード内で関数宣言が許可されている理由を説明するのに最適な仕事をしたからではなく、受け入れられました。しかし、コード内で関数定義が許可されない理由を説明するのに最適な仕事をしたので、それが実際の質問でした。具体的には、コード内関数の仮想実装がラムダの実装と異なる理由を具体的に説明します。+1
ジョナサンミー2015年

1
@JonathanMee:世界ではどうですか:「...この拒否の理由について考えられる情報源はありません。」入れ子関数の定義が許可されない理由を説明する(またはまったく説明しようとしない)のに最適な仕事と見なされます
Jerry Coffin 2015年

@JerryCoffin答えには、ラムダがすでにコード関数定義のスーパーセットであり、実装が不要になる理由の公式の根拠が含まれていました。「その関数がアクティブでない限り、結果のクロージャを使用することはできません。ローカル変数をクロージャに。」コンパイラーに課せられた追加の複雑さの分析が、私が受け入れた答えではなかった理由をあなたが尋ねていると思います。もしそうなら:あなたはラムダがすでに達成していることの難しさについて話します、コード定義では明らかにラムダとまったく同じように実装することができます。
ジョナサンミー2015年

31

さて、答えは「歴史的理由」です。Cでは、ブロックスコープで関数宣言を行うことができましたが、C ++の設計者は、そのオプションを削除してもメリットがありませんでした。

使用例は次のとおりです。

#include <iostream>

int main()
{
    int func();
    func();
}

int func()
{
    std::cout << "Hello\n";
}

IMOこれは悪い考えです。関数の実際の定義と一致しない宣言を提供することで間違いを犯しやすく、コンパイラーによって診断されない未定義の動作につながるからです。


10
「これは、一般的には悪いアイデア」とみなされる-引用が必要です。
リチャード・ホッジス

4
@RichardHodges:ええと、関数宣言はヘッダーファイルに属し、実装は.cまたは.cppファイルに属しているため、これらの宣言を関数定義内に含めると、これら2つのガイドラインのいずれかに違反します。
MSalters 2015

2
宣言が定義と異なるのをどのように防ぐのですか?
リチャードホッジス2015

1
@JonathanMee:使用している宣言が関数が定義されている場所で利用できない場合、コンパイラは宣言が定義と一致することをチェックしていない可能性があると言っています。したがって、ローカル宣言some_type f();と、別の変換単位での定義がある場合がありますanother_type f() {...}。コンパイラーはこれらが一致しないことを通知できずf、間違った宣言で呼び出すと未定義の動作が発生します。したがって、ヘッダーに宣言を1つだけ含め、関数が定義されている場所と使用されている場所にそのヘッダーを含めることをお勧めします。
マイクシーモア

6
あなたが言っていることは、ヘッダーファイルに関数宣言を入れるという一般的な方法が一般的に役立つということだと思います。誰もがそれに反対するとは思わない。私が理由を知らないのは、関数スコープで外部関数を宣言することは「一般的に悪い考えと見なされている」という主張です。
リチャードホッジス

23

あなたが与える例でvoid two(int)は、は外部関数として宣言されており、その宣言main関数のスコープ内でのみ有効です

現在のコンパイルユニット内のグローバル名前空間を汚染しないように、名前をtwo内部で使用できるようにするだけの場合は、これが妥当ですmain()

コメントへの応答の例:

main.cpp:

int main() {
  int foo();
  return foo();
}

foo.cpp:

int foo() {
  return 0;
}

ヘッダーファイルは必要ありません。コンパイルしてリンクする

c++ main.cpp foo.cpp 

コンパイルして実行すると、プログラムは期待どおりに0を返します。


twoとにかく汚染を引き起こすファイルで定義する必要もありませんか?
ジョナサンミー2015

1
@JonathanMeeいいえ、two()まったく異なるコンパイル単位で定義できます。
リチャードホッジス2015

それがどのように機能するかを理解するのに助けが必要です。宣言されたヘッダーを含める必要はありませんか?どの時点で宣言されますか?コードでそれを定義する方法がわかりません。どういうわけか、それを宣言するファイルを含めないのですか?
ジョナサンミー2015

5
@JonathanMeeヘッダーについて特別なことは何もありません。それらは宣言を置くのにちょうど便利な場所です。関数内の宣言は、ヘッダー内の宣言と同じように有効です。したがって、いいえ、リンク先のヘッダーを含める必要はありません(ヘッダーがまったくない場合もあります)。
キュービック

1
@JonathanMee C / C ++の用語では、定義と実装は同じものです。関数は何度でも宣言できますが、定義できるのは1回だけです。宣言は、.hで終わるファイルにある必要はありません-fooを呼び出す関数バー(本体でfooを宣言する)を持つファイルuse.cppと、fooを定義するファイルprovides.cppを持つことができます。リンクの手順を台無しにしない限り、問題なく動作します。
キュービック

19

これらのことを行うことができます。これは主に、実際にはそれほど難しいことではないためです。

コンパイラの観点からは、別の関数内に関数宣言を含めることは、実装するのが非常に簡単です。コンパイラには、関数内の宣言が関数内の他の宣言(たとえばint x;)を処理できるようにするメカニズムが必要です。

通常、宣言を解析するための一般的なメカニズムがあります。コンパイラを書いている人にとって、そのメカニズムが別の関数の内部または外部のコードを解析するときに呼び出されるかどうかはまったく問題ではありません-それは単なる宣言なので、宣言があることを十分に知っているときは、宣言を処理するコンパイラの部分を呼び出します。

実際、関数内でこれらの特定の宣言を禁止すると、おそらくさらに複雑になります。コンパイラーは、関数定義内のコードを既に調べているかどうかを確認し、それに基づいてこの特定の宣言を許可するか禁止するかを決定するために、完全に無償のチェックを行う必要があるためです。宣言。

それは、入れ子関数がどのように異なるのかという問題を残します。入れ子関数は、コード生成にどのように影響するかによって異なります。ネストされた関数を許可する言語(Pascalなど)では、通常、ネストされた関数のコードが、ネストされた関数の変数に直接アクセスできることを期待します。例えば:

int foo() { 
    int x;

    int bar() { 
        x = 1; // Should assign to the `x` defined in `foo`.
    }
}

ローカル関数がなければ、ローカル変数にアクセスするためのコードはかなり単純です。典型的な実装では、実行が関数に入ると、ローカル変数用のスペースのブロックがスタックに割り当てられます。すべてのローカル変数はその単一のブロックに割り当てられ、各変数はブロックの最初(または最後)からの単なるオフセットとして扱われます。たとえば、次のような関数について考えてみましょう。

int f() { 
   int x;
   int y;
   x = 1;
   y = x;
   return y;
}

コンパイラー(余分なコードを最適化していないと仮定)は、これとほぼ同等のコードを生成する可能性があります。

stack_pointer -= 2 * sizeof(int);      // allocate space for local variables
x_offset = 0;
y_offset = sizeof(int);

stack_pointer[x_offset] = 1;                           // x = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];     // y = x;
return_location = stack_pointer[y_offset];             // return y;
stack_pointer += 2 * sizeof(int);

特に、ローカル変数のブロックの先頭を指す1つの場所があり、ローカル変数へのすべてのアクセスはその場所からのオフセットとして行われます。

ネストされた関数では、それはもはや当てはまりません。代わりに、関数はそれ自体のローカル変数だけでなく、ネストされているすべての関数にローカルな変数にもアクセスできます。オフセットを計算する「stack_pointer」を1つだけ持つのではなく、スタックをさかのぼって、ネストされている関数にローカルなstack_pointersを見つける必要があります。

さて、ささいなケースでも、それほどひどいわけではありません。barfoo、の中にネストされている場合はbar、前のスタックポインタでスタックを検索して、fooの変数にアクセスできます。正しい?

違う!そうですね、これが当てはまる場合もありますが、必ずしもそうとは限りません。特に、bar再帰的である可能性があります。その場合、の特定の呼び出しはbar周囲の関数の変数を見つけるために、スタックをバックアップするほぼ任意の数のレベルを調べる必要がある場合があります。一般的に言えば、次の2つのいずれかを行う必要があります。スタックに追加のデータを配置して、実行時にスタックを検索して周囲の関数のスタックフレームを見つけることができるようにするか、またはポインタを効果的にに渡します。ネストされた関数の非表示パラメーターとしての周囲の関数のスタックフレーム。ああ、しかし、必ずしも周囲の関数が1つだけではありません。関数をネストできる場合は、おそらく(多かれ少なかれ)任意の深さでネストできるため、任意の数の非表示パラメーターを渡す準備ができている必要があります。つまり、通常、スタックフレームのリンクリストのようなものが周囲の関数にリンクされていることになります。

ただし、これは、「ローカル」変数へのアクセスが簡単なことではない可能性があることを意味します。変数にアクセスするための正しいスタックフレームを見つけることは簡単ではない可能性があるため、周囲の関数の変数へのアクセスも(少なくとも通常は)真のローカル変数へのアクセスよりも遅くなります。そしてもちろん、コンパイラは、適切なスタックフレームを見つけたり、任意の数のスタックフレームを介して変数にアクセスしたりするためのコードを生成する必要があります。

これは、Cが入れ子関数を禁止することによって回避していた複雑さです。さて、現在のC ++コンパイラが1970年代のビンテージCコンパイラとはかなり異なる種類の獣であることは確かに真実です。複数の仮想継承のようなものでは、C ++コンパイラは、どのような場合でも、これと同じ一般的な性質のものを処理する必要があります(つまり、そのような場合の基本クラス変数の場所を見つけることも重要です)。パーセンテージベースでは、ネストされた関数をサポートしても、現在のC ++コンパイラにそれほど複雑さは追加されません(gccなどの一部はすでにそれらをサポートしています)。

同時に、それはめったに多くの有用性を追加しません。あなたが何かを定義する場合は特に、働き関数の関数の内部のように、あなたは、ラムダ式を使用することができます。これが実際に作成するのは、関数呼び出し演算子(operator())をオーバーロードするオブジェクト(つまり、あるクラスのインスタンス)ですが、それでも関数のような機能を提供します。ただし、周囲のコンテキストからデータをキャプチャする(またはキャプチャしない)ことをより明確にします。これにより、まったく新しいメカニズムとその使用のための一連のルールを発明するのではなく、既存のメカニズムを使用できます。

結論:ネストされた宣言は最初は難しく、ネストされた関数は些細なことのように見えますが、多かれ少なかれ逆です。ネストされた関数は、実際にはネストされた宣言よりもサポートがはるかに複雑です。


5

最初のものは関数定義であり、許可されていません。明らかに、wtは、関数の定義を別の関数内に配置するための使用法です。

しかし、他の2つは単なる宣言です。int two(int bar);mainメソッド内で関数を使用する必要があると想像してください。ただし、main()関数の下で定義されているため、関数内の関数宣言により、その関数を宣言とともに使用できます。

同じことが3番目にも当てはまります。関数内のクラス宣言を使用すると、適切なヘッダーや参照を指定しなくても、関数内でクラスを使用できます。

int main()
{
    // This is legal, but why would I want this?
    int two(int bar);

    //Call two
    int x = two(7);

    class three {
        int m_iBar;
        public:
            three(int bar):m_iBar(13 + bar) {}
            operator int() {return m_iBar;}
    };

    //Use class
    three *threeObj = new three();

    return 0;
}

2
「減速」とは?「宣言」という意味ですか?
Peter Mortensen 2015年

4

この言語機能はCから継承され、Cの初期の頃に何らかの目的を果たしました(関数宣言のスコープは多分?)。この機能が現代のCプログラマーによって多く使用されているかどうかはわかりませんが、私はそれを心から疑っています。

だから、答えを要約すると:

最新のC ++ではこの機能の目的はありません(少なくとも私は知っています)。C++からCへの下位互換性のためにここにあります(おそらく:))。


以下のコメントに感謝します:

関数プロトタイプは、それが宣言されている関数にスコープされているため、#include。なしで外部関数/シンボルを参照することにより、よりきちんとしたグローバル名前空間を持つことができます。


目的は、名前空間の汚染を回避するために名前のスコープを制御することです。
リチャードホッジス2015

わかりました。グローバル名前空間を#includeで汚染せずに外部関数/シンボルを参照したい場合に、便利だと思います。ご指摘いただきありがとうございます。編集します。
mr.pd 2015

4

実際、おそらく役立つユースケースが1つあります。特定の関数が呼び出される(そしてコードがコンパイルされる)ことを確認したい場合は、周囲のコードが何を宣言していても、独自のブロックを開いて、その中で関数プロトタイプを宣言できます。(インスピレーションは元々、Johannes Schaub、https: //stackoverflow.com/a/929902/3150802、TeKa、https //stackoverflow.com/a/8821992/3150802からのものです)。

これは、制御できないヘッダーを含める必要がある場合、または不明なコードで使用される可能性のある複数行のマクロがある場合に特に役立ちます。

重要なのは、ローカル宣言が、最も内側の囲んでいるブロック内の以前の宣言に優先することです。それは微妙なバグをもたらす可能性がありますが(そして、C#では禁止されていると思います)、意識的に使用することができます。考えてみましょう:

// somebody's header
void f();

// your code
{   int i;
    int f(); // your different f()!
    i = f();
    // ...
}

ヘッダーがライブラリに属している可能性があるため、リンクは興味深いかもしれませんが、f()ライブラリが考慮されるまでに関数に解決されるようにリンカー引数を調整できると思います。または、重複するシンボルを無視するように指示します。または、ライブラリに対してリンクしません。


だからここで私を助けてfください、あなたの例ではどこで定義されますか?これらは戻り値の型によってのみ異なるため、関数の再定義エラーが発生することはありませんか?
ジョナサンミー2015

@JonathanMee hmmm ... f()は別の翻訳単位で定義できると思いました。しかし、想定されたライブラリに対してもリンクした場合、おそらくリンカーは失敗するでしょう。したがって、それを行うことはできません;-)、または少なくとも複数の定義を無視する必要があります。
ピーター-モニカを復活させる2015年

悪い例。関数の戻り値はC ++の関数のシグネチャの一部ではないため、C ++ではvoid f()との区別int f()はありません。2番目の宣言をに変更するint f(int)と、反対票が削除されます。
David Hammen 2015

@DavidHammeni = f();を宣言しvoid f()た後、コンパイルしてみてください。「区別なし」は真実の半分に過ぎません;-)。私は実際にオーバーロード不可能な関数「署名」を使用しました。そうしないと、パラメーターのタイプ/番号が異なる2つの関数がうまく共存できるため、C ++では状況全体が不要になるためです。
ピーター-モニカを復活させる2015年

@DavidHammen確かに、Shafikの回答を読んだ後、3つのケースがあると思います。1。署名はパラメーターが異なります。C ++では問題なく、単純なオーバーロードとベストマッチルールが機能します。2.署名はまったく変わりません。言語レベルでは問題ありません。機能は、目的の実装に対してリンクすることで解決されます。3.違いは戻り値の型のみです。そこ実証されたように、言語レベルでの問題は、。過負荷の解決は機能しません。我々は、異なるシグネチャを持つ関数を宣言しなければならない 適切にリンクします。
ピーター-モニカを復活させる2015年

3

これはOPの質問に対する回答ではなく、いくつかのコメントへの回答です。

コメントと回答のこれらの点に同意しません。1はネストされた宣言は無害であるとされており、2はネストされた定義は役に立たないということです。

1入れ子関数宣言の無害性の主な反例は、悪名高い最も厄介な解析です。IMOによって引き起こされる混乱の広がりは、ネストされた宣言を禁止する追加のルールを保証するのに十分です。

2ネストされた関数定義が役に立たないと主張されていることに対する最初の反例は、正確に1つの関数内の複数の場所で同じ操作を実行する必要があることです。これには明らかな回避策があります。

private:
inline void bar(int abc)
{
    // Do the repeating operation
}

public: 
void foo()
{
    int a, b, c;
    bar(a);
    bar(b);
    bar(c);
}

ただし、このソリューションは、多くの場合、クラス定義を多数のプライベート関数で汚染し、それぞれが1人の呼び出し元で使用されます。ネストされた関数宣言ははるかにクリーンになります。


1
これは私の質問の動機の良い要約になると思います。私がMVPを引用した元のバージョンを見ると、MVPは無関係であると言われている(私自身の質問の)コメントで却下され続けています:(コード宣言の潜在的に有害なものがまだここにあるのか理解できませんしかし、潜在的に有用なコードの定義ではありません、私はあなたに有益な例については+1を与えてくれた。。
ジョナサン・ミー

2

この質問に具体的に答える:

回答から、コード内宣言は名前空間の汚染を防ぐことができるようですが、私が聞きたかったのは、関数を宣言する機能は許可されているが、関数を定義する機能は許可されていない理由です。

このコードを検討するため:

int main()
{
  int foo() {

    // Do something
    return 0;
  }
  return 0;
}

言語設計者への質問:

  1. foo()他の機能で利用できるようにする必要がありますか?
  2. もしそうなら、その名前は何ですか?int main(void)::foo()
  3. (C ++の創始者であるCでは2は不可能であることに注意してください)
  4. ローカル関数が必要な場合は、すでに方法があります。ローカルで定義されたクラスの静的メンバーにします。では、同じ結果を達成するための別の構文方法を追加する必要がありますか?なぜそうするのですか?C ++コンパイラ開発者のメンテナンス負担が増えるのではないでしょうか?
  5. 等々...

明らかに、この動作はラムダに対して定義されていますか?関数をコードで定義しないのはなぜですか?
ジョナサンミー2015

ラムダは、関数オブジェクトを作成するための単なる省略形です。引数をキャプチャしないラムダの特殊なケースは、データメンバーを持たない関数オブジェクトを作成する場合と同様に、ローカル関数定義と同等です。
リチャードホッジス2015

私はちょうどそのラムダを指摘して、そしてコードで宣言された関数は、すでにすべてのあなたのポイントを解任します。「負担」の増加であってはなりません。
ジョナサンミー2015

@JonathanMeeについて強く感じている場合は、必ずRFCをc ++標準委員会に提出してください。
リチャードホッジス2015

Shafik Yaghmourの回答は、すでに行われていることをカバーしています。個人的には、関数を定義できない場合は、コードで関数を宣言する機能が削除されることを望んでいます。リチャード・ホッジスの答えは、コード宣言で宣言する機能がまだ必要な理由をうまく説明しています。
ジョナサンミー2015

1

GCCコンパイラでは、関数内で関数を宣言できることを指摘したいと思います。詳しくはこちらをご覧ください。また、ラムダがC ++に導入されたことで、この質問は少し時代遅れになりました。


他の関数内で関数ヘッダーを宣言する機能は、次の場合に役立ちます。

void do_something(int&);

int main() {
    int my_number = 10 * 10 * 10;
    do_something(my_number);

    return 0;
}

void do_something(int& num) {
    void do_something_helper(int&); // declare helper here
    do_something_helper(num);

    // Do something else
}

void do_something_helper(int& num) {
    num += std::abs(num - 1337);
}

ここには何がありますか?基本的に、mainから呼び出されることになっている関数があるので、通常のように前方宣言します。しかし、あなたは、この関数がそれがしていることを助けるために別の関数も必要としていることに気づきます。したがって、そのヘルパー関数をmainの上で宣言するのではなく、それを必要とする関数内で宣言すると、その関数とその関数からのみ呼び出すことができます。

私のポイントは、関数内で関数ヘッダーを宣言することは、関数カプセル化の間接的な方法である可能性があります。これにより、関数は、自分だけが認識している他の関数に委任することで、実行していることの一部を隠すことができ、ネストされたような錯覚を与えることができます。関数


ラムダインラインを定義できることを理解しました。関数をインラインで宣言できることは理解しましたが、それが最も厄介な解析の起源であるため、私の質問は、標準がプログラマーに怒りを誘発するだけの機能を維持するのかどうか、プログラマーが定義できるべきではないかということでした。インライン関数も?リチャードホッジスの答えは、私がこの問題の原因を理解するのに役立ちました。
ジョナサンミー

0

ネストされた関数宣言は、おそらく1.前方参照2.関数へのポインターを宣言し、限定されたスコープ内の他の関数を渡すことができるようにするために許可されます。

ネストされた関数の定義は、おそらく1.最適化2.再帰(定義された関数を囲んでネストしたもの)3。再入力4.同時実行性およびその他のマルチスレッドアクセスの問題のために許可されません。

私の限られた理解から:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.