Program Tip

다른 함수 안에 함수를 정의 할 수없는 이유는 무엇입니까?

programtip 2020. 10. 14. 20:54
반응형

다른 함수 안에 함수를 정의 할 수없는 이유는 무엇입니까?


이것은 람다 함수 질문이 아니며 람다를 변수에 할당 할 수 있다는 것을 알고 있습니다.

우리가 선언 할 수 있지만 코드 내부에 함수를 정의하지 않는 이유는 무엇입니까?

예를 들면 :

#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할까요?

편집하다:

답변에서 코드 내 선언이 네임 스페이스 오염을 방지 할 수있는 것 같지만, 내가 듣고 싶었던 것은 함수 선언 기능이 허용되었지만 함수 정의 기능이 허용되지 않는 이유입니다.


one허용되지 않는 이유 분명 하지 않습니다. 중첩 함수는 오래 전에 N0295 에서 제안되었습니다 .

중첩 함수를 C ++에 도입하는 방법에 대해 설명합니다. 중첩 함수는 잘 이해되고 있으며 컴파일러 공급 업체, 프로그래머 또는위원회의 노력이 거의 필요하지 않습니다. 중첩 함수는 상당한 이점을 제공합니다. [...]

분명히이 제안은 거부되었지만 온라인 회의록 1993이 없기 때문에이 거부의 근거에 대한 가능한 출처가 없습니다.

실제로이 제안은 가능한 대안으로 C ++에 대한 Lambda 표현식 및 클로저에 언급되어 있습니다.

C ++위원회 [SH93]에 대한 한 기사 [Bre88] 및 제안 N0295는 C ++에 중첩 함수를 추가 할 것을 제안합니다. 중첩 함수는 람다 식과 유사하지만 함수 본문 내에서 문으로 정의되며 해당 함수가 활성화되지 않으면 결과 클로저를 사용할 수 없습니다. 이러한 제안에는 각 람다 식에 대해 새 유형을 추가하는 것이 포함되지 않지만 대신 특수한 종류의 함수 포인터가 참조 할 수 있도록 허용하는 것을 포함하여 일반 함수와 비슷하게 구현합니다. 이 두 제안은 모두 C ++에 템플릿을 추가하기 이전에 있으므로 일반 알고리즘과 함께 중첩 함수를 사용하는 것에 대해서는 언급하지 않습니다. 또한 이러한 제안은 지역 변수를 클로저로 복사 할 수있는 방법이 없으므로 생성하는 중첩 함수는 둘러싸는 함수 외부에서 완전히 사용할 수 없습니다.

이제 람다가 있다는 점을 고려할 때, 문서에서 설명했듯이 중첩 함수는 동일한 문제에 대한 대안이며 중첩 함수에는 람다와 관련된 몇 가지 제한 사항이 있기 때문에 중첩 함수를 볼 가능성이 거의 없습니다.

질문의이 부분에 관해서는 :

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

이것이 원하는 함수를 호출하는 유용한 방법 인 경우가 있습니다. C ++ 표준 섹션 3.4.1 [basic.lookup.unqual] 초안 은 흥미로운 예를 제공합니다.

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)
}

음, 대답은 "역사적 이유"입니다. C에서는 블록 범위에서 함수 선언을 가질 수 있었지만 C ++ 디자이너는 해당 옵션을 제거하는 이점을 보지 못했습니다.

사용 예는 다음과 같습니다.

#include <iostream>

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

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

IMO는 함수의 실제 정의와 일치하지 않는 선언을 제공하여 실수를하기 쉽기 때문에 잘못된 생각이며 컴파일러가 진단하지 않는 정의되지 않은 동작을 유발합니다.


귀하가 제공하는 예제 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을 반환합니다.


실제로 그렇게하기가 그렇게 어렵지 않기 때문에 이런 일을 할 수 있습니다.

컴파일러의 관점에서, 다른 함수 내에 함수 선언을 갖는 것은 구현하기가 매우 간단합니다. 컴파일러에는 함수 내부의 선언이 함수 내부의 다른 선언 (예 :)을 처리 할 수있는 메커니즘이 필요 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);

특히, 지역 변수 블록의 시작을 가리키는 하나의 위치 가 있으며 지역 변수에 대한 모든 액세스는 해당 위치에서 오프셋으로 이루어집니다.

중첩 된 함수를 사용하면 더 이상 그렇지 않습니다. 대신 함수는 자체 지역 변수뿐만 아니라 중첩 된 모든 함수의 지역 변수에 액세스 할 수 있습니다. 오프셋을 계산하는 하나의 "stack_pointer"를 갖는 대신 스택을 백업하여 중첩 된 함수에 대한 로컬 stack_pointer를 찾아야합니다.

이제, 모두가 아니다 사소한 경우에 그 끔찍한 중 - 경우 bar의 중첩 된 내부는 foo다음 bar바로 액세스 이전의 스택 포인터의 스택을 볼 수 foo의 변수를. 권리?

잘못된! 음, 이것이 사실 일 수있는 경우가 있지만 반드시 그런 것은 아닙니다. 특히 bar재귀적일 수 있으며,이 경우 주어진 호출이bar might have to look some nearly arbitrary number of levels back up the stack to find the variables of the surrounding function. Generally speaking, you need to do one of two things: either you put some extra data on the stack, so it can search back up the stack at run-time to find its surrounding function's stack frame, or else you effectively pass a pointer to the surrounding function's stack frame as a hidden parameter to the nested function. Oh, but there's not necessarily just one surrounding function either--if you can nest functions, you can probably nest them (more or less) arbitrarily deep, so you need to be ready to pass an arbitrary number of hidden parameters. That means you typically end up with something like a linked list of stack frames to surrounding functions, and access to variables of surrounding functions is done by walking that linked list to find its stack pointer, then accessing an offset from that stack pointer.

그러나 이는 "로컬"변수에 대한 액세스가 사소한 문제가 아닐 수도 있음을 의미합니다. 변수에 액세스하기 위해 올바른 스택 프레임을 찾는 것은 사소한 일이 아니므로 주변 함수의 변수에 대한 액세스도 실제 로컬 변수에 액세스하는 것보다 (적어도 일반적으로) 느립니다. 그리고 물론 컴파일러는 올바른 스택 프레임을 찾고 임의의 수의 스택 프레임을 통해 변수에 액세스하는 코드를 생성해야합니다.

이것이 C가 중첩 함수를 금지함으로써 피했던 복잡성입니다. 현재의 C ++ 컴파일러가 1970 년대의 빈티지 C 컴파일러와는 다소 다른 종류의 짐승이라는 것은 확실히 사실입니다. 다중 가상 상속과 같은 경우 C ++ 컴파일러는 어떤 경우에도 이와 동일한 일반적인 특성을 처리해야합니다 (즉, 이러한 경우 기본 클래스 변수의 위치를 ​​찾는 것도 중요하지 않을 수 있음). 백분율 기준으로 중첩 된 함수를 지원한다고해서 현재 C ++ 컴파일러에 많은 복잡성이 추가되지는 않습니다 (그리고 gcc와 같은 일부는 이미 지원함).

동시에, 그것은 거의 유용성을 추가하지 않습니다. 특히 함수 내에서 함수처럼 작동 하는 것을 정의 하려면 람다 식을 사용할 수 있습니다. 이것이 실제로 생성하는 것은 함수 호출 연산자 ( operator()) 를 오버로드하는 객체 (즉, 일부 클래스의 인스턴스 )이지만 여전히 함수와 유사한 기능을 제공합니다. 그래도 주변 컨텍스트에서 데이터를 캡처 (또는 사용하지 않음)하여 완전히 새로운 메커니즘과 사용 규칙 세트를 개발하는 대신 기존 메커니즘을 사용할 수 있습니다.

결론 : 처음에는 중첩 된 선언이 어렵고 중첩 된 함수가 사소한 것처럼 보일 수도 있지만, 그 반대는 사실입니다. 중첩 된 함수는 실제로 중첩 된 선언보다 지원하기가 훨씬 더 복잡합니다.


첫 번째는 함수 정의이며 허용되지 않습니다. 당연히, wt는 함수의 정의를 다른 함수 안에 넣는 사용법입니다.

그러나 다른 두 가지는 단지 선언 일뿐입니다. int two(int bar);main 메소드 내에서 함수 를 사용해야한다고 상상해보십시오 . 그러나 main()함수 아래에 정의되어 있으므로 함수 내부의 함수 선언을 통해 해당 함수를 선언과 함께 사용할 수 있습니다.

세 번째도 마찬가지입니다. 함수 내부의 클래스 선언을 사용하면 적절한 헤더 또는 참조를 제공하지 않고도 함수 내부에서 클래스를 사용할 수 있습니다.

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;
}

이 언어 기능은 C에서 상속되었으며, C의 초기에 어떤 용도로 사용되었습니다 (함수 선언 범위 지정 가능?) . 이 기능이 현대의 C 프로그래머들에 의해 많이 사용되는지 모르겠고 진심으로 의심합니다.

So, to sum up the answer:

there is no purpose for this feature in modern C++ (that I know of, at least), it is here because of C++-to-C backward compatibility (I suppose :) ).


Thanks to the comment below:

Function prototype is scoped to the function it is declared in, so one can have a tidier global namespace - by referring to external functions/symbols without #include.


Actually, there is one use case which is conceivably useful. If you want to make sure that a certain function is called (and your code compiles), no matter what the surrounding code declares, you can open your own block and declare the function prototype in it. (The inspiration is originally from Johannes Schaub, https://stackoverflow.com/a/929902/3150802, via TeKa, https://stackoverflow.com/a/8821992/3150802).

This may be particularily useful if you have to include headers which you don't control, or if you have a multi-line macro which may be used in unknown code.

The key is that a local declaration supersedes previous declarations in the innermost enclosing block. While that can introduce subtle bugs (and, I think, is forbidden in C#), it can be used consciously. Consider:

// somebody's header
void f();

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

Linking may be interesting because chances are the headers belong to a library, but I guess you can adjust the linker arguments so that f() is resolved to your function by the time that library is considered. Or you tell it to ignore duplicate symbols. Or you don't link against the library.


This is not an answer to the OP question, but rather a reply to several comments.

I disagree with these points in the comments and answers: 1 that nested declarations are allegedly harmless, and 2 that nested definitions are useless.

1 The prime counterexample for the alleged harmlessness of nested function declarations is the infamous Most Vexing Parse. IMO the spread of confusion caused by it is enough to warrant an extra rule forbidding nested declarations.

2 The 1st counterexample to the alleged uselessness of nested function definitions is frequent need to perform the same operation in several places inside exactly one function. There is an obvious workaround for this:

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

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

However, this solution often enough contaminates the class definition with numerous private functions, each of which is used in exactly one caller. A nested function declaration would be much cleaner.


Specifically answering this question:

From the answers it seems that there in-code declaration may be able to prevent namespace pollution, what I was hoping to hear though is why the ability to declare functions has been allowed but the ability to define functions has been disallowed.

Because consider this code:

int main()
{
  int foo() {

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

Questions for language designers:

  1. Should foo() be available to other functions?
  2. If so, what should be its name? int main(void)::foo()?
  3. (Note that 2 would not be possible in C, the originator of C++)
  4. If we want a local function, we already have a way - make it a static member of a locally-defined class. So should we add another syntactic method of achieving the same result? Why do that? Wouldn't it increase the maintenance burden of C++ compiler developers?
  5. And so on...

Just wanted to point out that the GCC compiler allows you to declare functions inside functions. Read more about it here. Also with the introduction of lambdas to C++, this question is a bit obsolete now.


The ability to declare function headers inside other functions, I found useful in the following case:

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);
}

What do we have here? Basically, you have a function that is supposed to be called from main, so what you do is that you forward declare it like normal. But then you realize, this function also needs another function to help it with what it's doing. So rather than declaring that helper function above main, you declare it inside the function that needs it and then it can be called from that function and that function only.

My point is, declaring function headers inside functions can be an indirect method of function encapsulation, which allows a function to hide some parts of what it's doing by delegating to some other function that only it is aware of, almost giving an illusion of a nested function.


Nested function declarations are allowed probably for 1. Forward references 2. To be able to declare a pointer to function(s) and pass around other function(s) in a limited scope.

Nested function definitions are not allowed probably due to issues like 1. Optimization 2. Recursion (enclosing and nested defined function(s)) 3. Re-entrancy 4. Concurrency and other multithread access issues.

From my limited understanding :)

참고URL : https://stackoverflow.com/questions/29967202/why-cant-i-define-a-function-inside-another-function

반응형