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


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
첫 번째 one는 함수 정의 이고 다른 두 가지는 선언 입니다.
일부 프로그래머 친구

9
용어를 잘못 이해했다고 생각합니다. "우리가 선언 할 수 있지만 코드 내에서 함수를 정의 할 수없는 이유는 무엇입니까?"라고 질문하고 싶습니다 . 그리고 우리가 그것에있는 동안 당신은 아마도 " 함수 내부"를 의미 할 것입니다 . 모두 "코드"입니다.
Peter-Monica 복원

14
왜 언어에 기이하고 불일치가 있는지 묻는다면, 그것은 수십 년에 걸쳐 다양한 아이디어를 가진 많은 사람들의 작업을 통해 다른시기에 다른 이유로 발명 된 언어에서 진화했기 때문입니다. 왜 이런 특별한 특징이 있는지 묻는다면 : 아무도 (지금까지) 로컬 함수 정의가 표준화하기에 충분히 유용하다고 생각하지 않았기 때문입니다.
Mike Seymour

4
@MikeSeymour가 제대로 맞았습니다. C는 파스칼처럼 구조화되어 있지 않으며 항상 최상위 수준 함수 정의 만 허용됩니다. 그래서 그 이유는 역사적이며 그것을 바꿀 필요가 없습니다. 함수 선언 이 가능하다는 것은 일반적으로 가능한 범위 선언의 결과 일뿐입니다. 기능에 대한 금지는 추가 규칙을 의미합니다.
Peter-Monica 복원

3
@JonathanMee : 아마도 일반적으로 선언은 블록으로 허용되며 함수 선언을 특별히 금지 할 특별한 이유가 없기 때문일 것입니다. 특별한 경우없이 모든 선언을 허용하는 것이 더 간단합니다. 그러나 "왜"는 실제로 대답 할 수있는 질문이 아닙니다. 언어는 그것이 진화 한 방식이기 때문입니다.
Mike Seymour

답변:


41

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

1
3.4.1 예제에 대한 질문 : main의 호출자가 단순히 ::g(parm, 1)전역 네임 스페이스에서 함수를 호출하기 위해 작성할 수 없었 습니까? 아니면 g(parm, 1.0f);원하는 것과 더 잘 일치하는 결과를 호출 해야 g합니까?
Peter-Monica 복원

@PeterSchneider 나는 거기에 너무 강한 진술을했고 그것을 조정했습니다.
Shafik Yaghmour 2015

1
여기에 주석을 추가하고 싶습니다.이 답변은 코드 함수 선언이 허용되는 이유를 설명하는 최선의 작업을 수행했기 때문에 허용되지 않았습니다. 그러나 코드 함수 정의가 허용되지 않는 이유를 설명하는 최선의 작업을 수행했기 때문에 실제 질문이었습니다. 그리고 특히 코드 함수의 가상 구현이 람다 구현과 다른 이유를 구체적으로 설명합니다. +1
Jonathan Mee

1
@JonathanMee : 세상에서 어떻게 : "...이 거부에 대한 근거에 대한 가능한 출처가 없습니다." 중첩 된 함수 정의가 허용되지 않는 이유를 설명하는 가장 좋은 직업으로 자격을 갖추십시오 (또는 심지어 그것을 설명하려고 시도하는 것까지?)
Jerry Coffin

@JerryCoffin 대답에는 왜 람다가 이미 코드 함수 정의의 슈퍼 세트가되어 구현이 불필요하다는 공식적인 근거가 포함되어 있습니다. "그 함수가 활성화되지 않으면 결과 클로저를 사용할 수 없습니다. 지역 변수를 클로저에 넣습니다. " 컴파일러에 대한 추가 복잡성에 대한 분석이 내가 받아 들인 대답이 아닌 이유를 묻고 있다고 가정합니다. 만약 그렇다면 : 람다가 이미 달성 한 일의 어려움에 대해 말하면 코드 정의에서 람다와 똑같이 구현 될 수 있습니다.
Jonathan Mee 2015 년

31

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

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

#include <iostream>

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

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

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


10
"이것은 일반적으로 나쁜 생각으로 간주됩니다."-인용이 필요합니다.
Richard Hodges

4
@RichardHodges : 음, 함수 선언은 헤더 파일에 속하고 .c 또는 .cpp 파일의 구현에 속하므로 이러한 선언을 함수 정의 내에 포함하는 것은 두 지침 중 하나를 위반합니다.
MSalters

2
선언이 정의와 다른 것을 어떻게 방지합니까?
Richard Hodges 2015

1
@JonathanMee : 함수가 정의 된 곳에서 사용중인 선언을 사용할 수없는 경우 컴파일러가 선언이 정의와 일치하는지 확인하지 않을 수 있습니다. 따라서 지역 선언 some_type f();과 다른 번역 단위의 정의 가있을 수 있습니다 another_type f() {...}. 컴파일러는 이것이 일치하지 않는다고 말할 수 없으며 f잘못된 선언으로 호출 하면 정의되지 않은 동작이 발생합니다. 따라서 헤더에 선언이 하나만 있고 함수가 정의 된 위치와 사용되는 위치에 해당 헤더를 포함하는 것이 좋습니다.
Mike Seymour

6
당신이 말하고있는 것은 헤더 파일에 함수 선언을 넣는 일반적인 관행이 일반적으로 유용하다는 것입니다. 누구도 그것에 동의하지 않을 것이라고 생각합니다. 내가보기에 이유가없는 것은 함수 범위에서 외부 함수를 선언하는 것이 '일반적으로 나쁜 생각으로 간주된다'는 주장입니다.
Richard Hodges 2015

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또한함으로써 오염 어쨌든 원인이되는 파일에 정의되어야한다?
Jonathan Mee

1
@JonathanMee no, two()완전히 다른 컴파일 단위로 정의 할 수 있습니다.
Richard Hodges 2015

어떻게 작동하는지 이해하는 데 도움이 필요합니다. 선언 된 헤더를 포함 할 필요가 없습니까? 어느 시점에서 선언 될까요? 코드에서 어떻게 정의 할 수 있는지 모르겠고 어떻게 든 그것을 선언하는 파일을 포함하지 않습니까?
Jonathan Mee 2015

5
@JonathanMee 헤더에는 특별한 것이 없습니다. 선언을하기에 편리한 곳입니다. 함수 내의 선언은 헤더 내의 선언만큼 유효합니다. 따라서 링크하려는 항목의 헤더를 포함 할 필요가 없습니다 (헤더가 전혀 없을 수도 있음).
Cubic

1
@JonathanMee C / C ++ 용어에서 정의와 구현은 동일합니다. 원하는만큼 자주 함수를 선언 할 수 있지만 한 번만 정의 할 수 있습니다. 선언은 .h로 끝나는 파일에있을 필요가 없습니다. foo (본문에 foo 선언)를 호출하는 함수 막대가있는 use.cpp 파일과 foo를 정의하는 .cpp를 제공하는 파일을 가질 수 있습니다. 연결 단계를 엉망으로 만들지 않는 한 잘 작동합니다.
Cubic

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

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

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

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

잘못된! 이것이 사실 일 수있는 경우가 있지만 반드시 그런 것은 아닙니다. 특히,bar 재귀적일 수 있습니다.이 경우 주어진 호출은bar주변 함수의 변수를 찾기 위해 스택을 백업하는 거의 임의의 수의 수준을 찾아야 할 수도 있습니다. 일반적으로 두 가지 중 하나를 수행해야합니다. 스택에 추가 데이터를 넣어 런타임에 스택을 검색하여 주변 함수의 스택 프레임을 찾을 수 있도록하거나, 아니면 포인터를 효과적으로 전달합니다. 중첩 된 함수에 대한 숨겨진 매개 변수로 주변 함수의 스택 프레임. 오,하지만 주변 함수가 반드시 하나만있는 것은 아닙니다. 함수를 중첩 할 수 있다면 아마도 임의의 깊이 중첩 (더 많거나 적음) 할 수 있으므로 임의의 수의 숨겨진 매개 변수를 전달할 준비가되어 있어야합니다. 즉, 일반적으로 스택 프레임을 주변 기능에 연결 한 목록과 같은 결과를 얻게됩니다.

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

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

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

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


5

첫 번째는 함수 정의이며 허용되지 않습니다. 당연히, 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;
}

2
"감속"이란 무엇입니까? "선언"을 의미합니까?
Peter Mortensen

4

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

따라서 답을 요약하면 다음과 같습니다.

현대 C ++ 에서는이 기능에 대한 목적이 없습니다 (적어도 알고 있습니다). C ++에서 C 로의 이전 버전과의 호환성 (내 생각에는 :)) 때문입니다.


아래 의견에 감사드립니다.

함수 프로토 타입은 선언 된 함수로 범위가 지정되므로 .NET없이 외부 함수 / 기호를 참조하여 더 깔끔한 전역 네임 스페이스를 가질 수 있습니다 #include.


목적은 글로벌 네임 스페이스 오염을 피하기 위해 이름의 범위를 제어하는 ​​것입니다.
Richard Hodges 2015

좋아, #include로 전역 네임 스페이스를 오염시키지 않고 외부 함수 / 기호를 참조하려는 상황에 유용하다고 생각합니다! 지적 해 주셔서 감사합니다. 편집하겠습니다.
mr.pd

4

실제로, 생각할 수있는 유용한 사용 사례가 하나 있습니다. 특정 함수가 호출되고 코드가 컴파일되는지 확인하려면 주변 코드가 선언하는 내용에 관계없이 자체 블록을 열고 그 안에 함수 프로토 타입을 선언 할 수 있습니다. (원래는 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. 당신의 예에서 정의되는 곳은 어디 입니까? 반환 유형에 의해서만 다르기 때문에 함수 재정의 오류가 발생하지 않습니까?
Jonathan Mee

@JonathanMee hmmm ... f ()는 다른 번역 단위로 정의 될 수 있다고 생각했습니다. 그러나 아마도 당신이 가정 된 라이브러리에 대해 링크한다면 아마도 링커는 망설 일 것입니다. 저는 당신이 옳다고 생각합니다. 그래서 당신은 그것을 할 수 없습니다 ;-), 또는 적어도 여러 정의를 무시해야합니다.
Peter-Monica 복원

나쁜 예. 사이에 차이가 없습니다 void f()int f()함수의 반환 값은 C에서 함수의 서명 ++의 일부가 아니므로 C ++에서이. 두 번째 선언을로 변경하면 int f(int)내 반대표를 제거하겠습니다.
David Hammen 2015

@DavidHammen i = f();선언 후 컴파일 을 시도하십시오 void f(). "구별 없음"은 진실의 절반에 불과합니다 ;-). 실제로 오버로드 할 수없는 함수 "서명"을 사용했습니다. 그렇지 않으면 매개 변수 유형 / 번호가 다른 두 함수가 행복하게 공존 할 수 있기 때문에 C ++에서 전체 상황이 필요하지 않기 때문입니다.
피터 - 분석 재개 모니카

@DavidHammen 사실, Shafik의 답변을 읽은 후 세 가지 경우가 있다고 생각합니다. 1. 서명은 매개 변수가 다릅니다. C ++에서는 문제가 없으며 간단한 오버로드 및 최적 일치 규칙이 작동합니다. 2. 서명은 전혀 다르지 않습니다. 언어 수준에서는 문제가 없습니다. 기능은 원하는 구현에 연결하여 해결됩니다. 3. 차이점은 반환 유형에만 있습니다. 이다 입증 된 바와 같이 언어 수준에서 문제는; 과부하 해결이 작동하지 않습니다. 우리는 다른 서명을 가진 함수를 선언해야 하고 적절하게 연결합니다.
피터 - 분석 재개 모니카

3

이것은 OP 질문에 대한 답변이 아니라 여러 의견에 대한 답변입니다.

나는 주석과 답변에서 다음과 같은 점에 동의하지 않습니다. 1 중첩 선언은 무해하다고 주장하고 2 중첩 정의는 쓸모가 없습니다.

1 중첩 함수 선언의 무해 성에 대한 주요 반례는 악명 높은 Most Vexing Parse 입니다. IMO로 인한 혼란의 확산은 중첩 선언을 금지하는 추가 규칙을 보증하기에 충분합니다.

2 중첩 된 함수 정의가 쓸모 없다고 주장되는 첫 번째 반례는 정확히 하나의 함수 내의 여러 위치에서 동일한 작업을 자주 수행해야한다는 것입니다. 이에 대한 명백한 해결 방법이 있습니다.

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

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

그러나이 솔루션은 종종 정확히 한 명의 호출자에서 사용되는 수많은 개인 함수로 클래스 정의를 오염시킵니다. 중첩 된 함수 선언은 훨씬 더 깔끔합니다.


1
나는 이것이 내 질문의 동기를 잘 요약한다고 생각합니다. MVP를 인용 한 원래 버전을 보면 MVP가 관련이 없다는 의견 (내 질문에 대한)에서 계속 무시 당하고 있습니다. (코드 선언에서 잠재적으로 유해한 내용이 여전히 여기에 있는지 파악할 수 없습니다. , 그러나 코드 정의에서 잠재적으로 유용한 것은 아닙니다. 유익한 예제에 대해 +1을주었습니다
Jonathan Mee

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. 등등...

분명히이 동작은 람다에 대해 정의됩니까? 코드에 정의 된 함수가 아닌 이유는 무엇입니까?
Jonathan Mee

람다는 단순히 함수 객체를 작성하는 약어입니다. 인수를 캡처하지 않는 람다의 특수한 경우는 데이터 멤버가없는 함수 개체를 작성하는 것처럼 로컬 함수 정의와 동일합니다.
Richard Hodges 2015

난 그냥 그 람다을 지적했다, 코드에서 선언 된 함수는 이미 모든 포인트를 닫습니다. "부담"이 증가해서는 안됩니다.
Jonathan Mee 2015

@JonathanMee에 대해 강하게 느끼면 반드시 RFC를 C ++ 표준위원회에 제출하십시오.
Richard Hodges 2015

Shafik Yaghmour의 답변 은 이미 완료된 것을 다룹니다. 저는 개인적으로 함수를 정의 할 수없는 경우 코드에서 함수를 선언하는 기능이 제거되는 것을보고 싶습니다. Richard Hodges의 대답 은 코드 선언에서 선언 할 수있는 능력이 여전히 필요한 이유를 설명하는 데 효과적입니다.
Jonathan Mee

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 위에 해당 도우미 함수를 선언하는 대신 필요한 함수 내에서 선언 한 다음 해당 함수와 해당 함수에서만 호출 할 수 있습니다.

내 요점은 함수 내에서 함수 헤더를 선언하는 것이 함수 캡슐화의 간접적 인 방법이 될 수 있다는 것입니다.이 방법을 사용하면 함수가 자신 만 알고있는 다른 함수에 위임하여 함수가 수행하는 작업의 일부를 숨길 수 있으며 거의 중첩 된 것처럼 보입니다. 기능 .


람다를 인라인으로 정의 할 수 있다는 것을 이해했습니다. 나는 우리가 함수를 인라인으로 선언 할 수 있다는 것을 이해했지만 그것이 가장 성가신 구문 분석 의 기원 이기 때문에 내 질문은 표준이 프로그래머에게 분노를 유발하는 기능을 유지할 것인지, 프로그래머가 인라인 기능도? Richard Hodges의 대답 은이 문제의 원인을 이해하는 데 도움이되었습니다.
Jonathan Mee 2016

0

중첩 된 함수 선언은 아마도 1에 대해 허용됩니다. 포워드 참조 2. 함수에 대한 포인터를 선언하고 제한된 범위에서 다른 함수를 전달할 수 있습니다.

중첩 된 함수 정의는 아마도 다음과 같은 문제로 인해 허용되지 않습니다. 1. 최적화 2. 재귀 (포함 및 중첩 정의 된 함수) 3. 재진입 4. 동시성 및 기타 다중 스레드 액세스 문제.

내 제한된 이해에서 :)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.