C ++에서 함수 내부에 함수를 가질 수 있습니까?


225

나는 다음과 같은 것을 의미한다 :

int main() 
{
  void a() 
  {
      // code
  }
  a();

  return 0;
}

1
왜 이러려고합니까? 목적을 설명하면 누군가가 목표를 달성하는 올바른 방법을 말해 줄 수 있습니다.
Thomas Owens

3
gcc는 비표준 확장으로 중첩 함수 를 지원합니다 . 그러나 gcc를 사용하더라도 사용하지 않는 것이 좋습니다. 그리고 C ++ 모드에서는 어쨌든 사용할 수 없습니다.
Sven Marnach

27
@ 토마스 :의 범위를 줄이는 것이 좋을까요? 함수의 함수는 다른 언어의 일반적인 기능입니다.
Johan Kotlinski

64
그는 중첩 함수에 대해 이야기하고 있습니다. 클래스 내에서 다음 클래스를 사용할 수있는 것과 마찬가지로 함수 내에 함수를 중첩하려고합니다. 사실, 가능하다면 가능했던 상황도있었습니다. 이를 가능하게하는 언어 (예 : F #)가 있으며, 매우 구체적인 컨텍스트 외부에서 쓸모없는 수십 개의 도우미 함수로 라이브러리를 오염시키지 않으면 서 코드를 훨씬 더 명확하고 읽기 쉽고 유지 관리 할 수 ​​있다고 말할 수 있습니다. ;)
Mephane

16
@Thomas-중첩 함수는 현재 범위를 둘러싸는 범위 내에서 일반적으로 사용 되지 않는 함수로 채우지 않고도 복잡한 함수 / 알고리즘을 끊는 훌륭한 메커니즘이 될 수 있습니다 . Pascal과 Ada는 (IMO) 멋진 지원을 받았습니다. 스칼라와 다른 많은 오래된 / 새로운 존경받는 언어와 동일합니다. 다른 기능과 마찬가지로 악용 될 수도 있지만 이는 개발자의 기능입니다. IMO, 그들은 그보다 훨씬 더 유익했습니다.
luis.espinal

답변:


271

Modern C ++-람다가 있습니다!

현재 버전의 c ++ (C ++ 11, C ++ 14 및 C ++ 17)에서는 함수 내부에 람다 형식으로 함수를 사용할 수 있습니다.

int main() {
    // This declares a lambda, which can be called just like a function
    auto print_message = [](std::string message) 
    { 
        std::cout << message << "\n"; 
    };

    // Prints "Hello!" 10 times
    for(int i = 0; i < 10; i++) {
        print_message("Hello!"); 
    }
}

Lambdas는 ** 기준 별 캡처 *를 통해 로컬 변수를 수정할 수도 있습니다. 참조 별 캡처를 사용하면 람다는 람다 범위에서 선언 된 모든 지역 변수에 액세스 할 수 있습니다. 정상적으로 수정하고 변경할 수 있습니다.

int main() {
    int i = 0;
    // Captures i by reference; increments it by one
    auto addOne = [&] () {
        i++; 
    };

    while(i < 10) {
        addOne(); //Add 1 to i
        std::cout << i << "\n";
    }
}

C ++ 98 및 C ++ 03-직접은 아니지만 로컬 클래스 내부의 정적 함수 사용

C ++은이를 직접 지원하지 않습니다.

즉, 로컬 클래스를 가질 수 있고 함수 ( static또는 비 static)를 가질 수 있으므로 약간의 문제가 있지만 확장 할 수 있습니다.

int main() // it's int, dammit!
{
  struct X { // struct's as good as class
    static void a()
    {
    }
  };

  X::a();

  return 0;
}

그러나 나는 연습에 의문을 제기했다. 모든 사람은 (물론, 당신이 지금, 어쨌든 알고 :)그들은 그것들을 가지고 있지에 사용되도록) C ++의 지역 기능을 지원하지 않습니다. 그러나 그들은 그 kludge에 사용되지 않습니다. 이 코드에서 로컬 기능을 허용하기 위해 실제로 만 존재하는지 확인하는 데 꽤 오랜 시간을 소비했습니다. 안좋다.


3
Main은 리턴 타입에 대해 놀랄만하다면 두 개의 인수를 취합니다. :) (또는 선택 사항이지만 요즘 돌아올 수는 없습니까? 계속 유지할 수 없습니다.)
Leo Davidson

3
이것은 나쁘다-그것은 좋고 깨끗한 코드의 모든 규칙을 어긴 다. 이것이 좋은 아이디어 인 단일 인스턴스를 생각할 수 없습니다.
Thomas Owens

19
@Thomas Owens : 콜백 함수가 필요하고 다른 네임 스페이스를 오염시키지 않으려면 좋습니다.
레오 데이비슨

9
:이 표준은 주요 두 허용 형태가 말한다 : @Leo int main()int main(int argc, char* argv[])
존 Dibling이

8
이 표준은 말한다 int main()하고 int main(int argc, char* argv[])지원해야하며, 다른 사람은 지원 될 수 있지만, 그들은 모두 반환 INT 있습니다.
JoeG

260

모든 의도와 목적을 위해 C ++은 람다 를 통해이를 지원합니다 . 1

int main() {
    auto f = []() { return 42; };
    std::cout << "f() = " << f() << std::endl;
}

여기에 f로컬 함수로 작동하는 람다 객체가 있습니다.main 있습니다. 함수가 로컬 오브젝트에 액세스 할 수 있도록 캡처를 지정할 수 있습니다.

내부적 fA는 함수 객체 (AN을 제공하는 타입의 객체 즉, operator()). 함수 객체 유형은 람다를 기반으로 컴파일러에 의해 생성됩니다.


C ++ 11 이후 1


5
아, 깔끔하다! 나는 그것을 생각하지 않았다. 이것은 내 생각보다 훨씬 낫 +1습니다.
sbi

1
@ sbi : 실제로 과거에 이것을 시뮬레이션하기 위해 로컬 구조체를 사용했습니다 (예, 적절하게 부끄러워합니다). 그러나 로컬 구조체가 클로저를 생성하지 않으므로 로컬 변수에 액세스 할 수 없기 때문에 유용성이 제한됩니다. 생성자를 통해 명시 적으로 전달하고 저장해야합니다.
Konrad Rudolph

1
@ Konrad : 또 다른 문제는 C ++ 98에서 로컬 유형을 템플릿 매개 변수로 사용해서는 안된다는 것입니다. 그러나 C ++ 1x가 그 제한을 해제했다고 생각합니다. (또는 C ++ 03입니까?)
sbi

3
@ luis : Fred와 동의해야합니다. 당신은 그들이 가지고 있지 않은 람다에 의미를 부여하고 있습니다 (C ++ 또는 내가 작업 한 다른 언어 는 아닙니다 - 파이썬과 Ada는 포함 하지 않았습니다 ). 또한 C ++에는 로컬 함수, 기간이 없으므로 C ++에서 구별하는 것은 의미가 없습니다. 람다 만 있습니다. 함수와 같은 것의 범위를 함수로 제한하려면 다른 답변에서 언급 된 람다 또는 로컬 구조체 만 선택할 수 있습니다. 나는 후자가 실제로 관심을 갖기에는 너무 복잡하다고 말하고 싶습니다.
Konrad Rudolph

2
@AustinWBryan 아니요, C ++의 람다는 단지 functors의 구문 설탕이며 오버 헤드가 없습니다. 이 웹 사이트 어딘가에 더 자세한 질문이 있습니다.
Konrad Rudolph

42

로컬 클래스는 이미 언급되었지만 다음은 operator () 오버로드 및 익명 클래스를 사용하여 로컬 함수로 더 많이 표시 할 수있는 방법입니다.

int main() {
    struct {
        unsigned int operator() (unsigned int val) const {
            return val<=1 ? 1 : val*(*this)(val-1);
        }
    } fac;

    std::cout << fac(5) << '\n';
}

나는 이것을 사용하는 것에 대해 조언하지 않지만, 그것은 단지 재미있는 트릭입니다 (할 수는 있지만 imho는해서는 안됩니다).


2014 년 업데이트 :

C ++ 11이 등장하면서 JavaScript를 약간 연상시키는 구문을 가진 로컬 함수를 사용할 수 있습니다.

auto fac = [] (unsigned int val) {
    return val*42;
};

1
이어야한다 operator () (unsigned int val), 당신은 괄호 세트가 없습니다.
Joe D

1
실제로,이 functor를 stl 함수 또는 알고리즘 (예 std::sort(): 또는) 에 전달해야하는 경우이 작업은 완벽하게 합리적 std::for_each()입니다.
Dima

1
@Dima : 불행히도 C ++ 03에서는 로컬로 정의 된 형식을 템플릿 인수로 사용할 수 없습니다. C ++ 0x는 이것을 수정하지만 훨씬 더 멋진 람다 솔루션을 제공하므로 여전히 그렇게하지 않을 것입니다.
Ben Voigt

죄송합니다. 내 잘못이야. 그러나 여전히 이것은 재미있는 재미있는 트릭이 아닙니다. 그것이 허락된다면 그것은 유용한 일이었을 것입니다. :)
Dima

3
재귀가 지원됩니다. 그러나 auto변수를 선언하는 데 사용할 수 없습니다 . Stroustrup은 function<void(char*b, char*e)> rev=[](char*b, char*e) { if( 1<e-b ) { swap( *b, *--e); rev(++b,e); } };시작 및 종료 포인터가 지정된 문자열을 뒤집기위한 예제를 제공합니다 .
시조

17

아니.

무엇을하려고합니까?

해결 방법 :

int main(void)
{
  struct foo
  {
    void operator()() { int a = 1; }
  };

  foo b;
  b(); // call the operator()

}

2
클래스 인스턴스화 접근 방식에는 메모리 할당이 제공되므로 정적 접근 방식이 지배적입니다.
ManuelSchneid3r

14

C ++ 11부터는 올바른 람다를 사용할 수 있습니다 . 자세한 내용은 다른 답변을 참조하십시오.


오래된 대답 : 당신은 일종의 할 수는 있지만 더미 클래스를 속이고 사용해야합니다.

void moo()
{
    class dummy
    {
    public:
         static void a() { printf("I'm in a!\n"); }
    };

    dummy::a();
    dummy::a();
}

대신 노이즈를 추가하는 IMO를 추가하는 개체를 만드는 것 외에는 확실하지 않습니다. 네임 스페이스로 할 수있는 영리한 일이 없다면 생각할 수 없으며 아마도 우리가 이미 존재하는 것 이상으로 언어를 남용하는 것은 좋지 않습니다. :)
Leo Davidson

더미 제거 ::는 다른 답변 중 하나입니다.
Sebastian Mach

8

다른 사람들이 언급했듯이 gcc에서 gnu 언어 확장을 사용하여 중첩 함수를 사용할 수 있습니다. 당신 (또는 당신의 프로젝트)이 gcc 툴체인을 고수한다면, 코드는 대부분 gcc 컴파일러가 목표로하는 다양한 아키텍처에서 이식 가능합니다.

그러나 다른 툴체인으로 코드를 컴파일해야 할 수있는 요구 사항이있을 경우 그러한 확장을 피할 수 있습니다.


또한 중첩 함수를 사용할 때주의를 기울였습니다. 복잡하지만 응집력있는 코드 블록 (외부 / 일반적인 용도로 사용되지 않는 코드)의 구조를 관리하는 데 유용한 솔루션입니다. 또한 네임 스페이스 오염을 제어하는 ​​데 매우 유용합니다 (자연적으로 복잡한 / 장황한 언어로 된 긴 수업.)

그러나 무엇이든 마찬가지로 학대에 노출 될 수 있습니다.

C / C ++가 표준과 같은 기능을 지원하지 않는 것은 슬픈 일입니다. 대부분의 파스칼 변형과 Ada는 거의 모든 Algol 기반 언어를 지원합니다. JavaScript와 동일합니다. 스칼라와 같은 현대 언어와 동일합니다. Erlang, Lisp 또는 Python과 같은 훌륭한 언어와 동일합니다.

그리고 C / C ++과 마찬가지로 불행히도 Java (내 삶의 대부분을 얻음)는 그렇지 않습니다.

클래스와 클래스의 메소드 사용을 중첩 함수의 대안으로 제안하는 여러 포스터가 있기 때문에 여기에서 Java를 언급합니다. 그리고 이것은 Java의 일반적인 해결 방법이기도합니다.

짧은 대답 : 아니요.

그렇게하면 클래스 계층 구조에서 인공적이고 불필요하게 복잡 해지는 경향이 있습니다. 모든 것이 동일하기 때문에 실제 도메인을 가능한 한 단순하게 나타내는 클래스 계층 구조 (및 포함 네임 스페이스 및 범위)가 이상적입니다.

중첩 된 함수는 "개인"기능 내 복잡성을 처리하는 데 도움이됩니다. 이러한 시설이 없기 때문에 "개인"복잡성을 자신의 클래스 모델로 전파하지 않도록 노력해야합니다.

소프트웨어 (및 모든 엔지니어링 분야)에서 모델링은 절충 문제입니다. 따라서 실제로는 이러한 규칙 (또는 지침)에 대한 예외가 정당화됩니다. 하지만 조심해서 진행하십시오.


8

C ++에는 로컬 함수를 사용할 수 없습니다. 그러나 C ++ 11에는 람다가 있습니다. 있습니다. 람다는 기본적으로 함수처럼 작동하는 변수입니다.

람다는 유형이 있습니다 std::function( 실제로 사실 은 아니지만 대부분의 경우 그렇습니다 ). 이 유형을 사용하려면을 수행해야합니다 #include <functional>. std::function템플릿으로, 반환 형식과 인수 형식을 템플릿 인수로하여 구문을 사용합니다 std::function<ReturnType(ArgumentTypes). 예를 들어,을 std::function<int(std::string, float)>반환 하고 하나의 int인수 std::string와 하나의 인수를 취하는 람다 float입니다. 가장 일반적인 것은std::function<void()> , 아무것도 반환하지 않으며 인수를 사용하지 않습니다.

람다가 선언되면 구문을 사용하여 일반 함수처럼 호출됩니다. lambda(arguments) 됩니다.

람다를 정의하려면 구문을 사용하십시오 [captures](arguments){code}(다른 방법은 있지만 여기서는 언급하지 않습니다). arguments람다가 취하는 인수이며 code람다가 호출 될 때 실행되어야하는 코드입니다. 일반적으로 [=]또는 [&]캡처로 넣습니다 . [=]값이 값으로 정의되는 범위의 모든 변수를 캡처한다는 것을 의미합니다. 즉, 람다가 선언되었을 때의 값을 유지합니다. [&]즉, 참조로 범위의 모든 변수를 캡처한다는 의미입니다. 즉, 항상 현재 값을 가지지 만 메모리에서 지워지면 프로그램이 중단됩니다. 여기 몇 가지 예가 있어요.

#include <functional>
#include <iostream>

int main(){
    int x = 1;

    std::function<void()> lambda1 = [=](){
        std::cout << x << std::endl;
    };
    std::function<void()> lambda2 = [&](){
        std::cout << x << std::endl;
    };

    x = 2;
    lambda1();    //Prints 1 since that was the value of x when it was captured and x was captured by value with [=]
    lambda2();    //Prints 2 since that's the current value of x and x was captured by value with [&]

    std::function<void()> lambda3 = [](){}, lambda4 = [](){};    //I prefer to initialize these since calling an uninitialized lambda is undefined behavior.
                                                                 //[](){} is the empty lambda.

    {
        int y = 3;    //y will be deleted from the memory at the end of this scope
        lambda3 = [=](){
            std::cout << y << endl;
        };
        lambda4 = [&](){
            std::cout << y << endl;
        };
    }

    lambda3();    //Prints 3, since that's the value y had when it was captured

    lambda4();    //Causes the program to crash, since y was captured by reference and y doesn't exist anymore.
                  //This is a bit like if you had a pointer to y which now points nowhere because y has been deleted from the memory.
                  //This is why you should be careful when capturing by reference.

    return 0;
}

이름을 지정하여 특정 변수를 캡처 할 수도 있습니다. 이름을 지정하면 값으로 캡처되고 이름을 &앞에 지정하면 참조로 캡처됩니다. 예를 들어, 참조로 캡처되는 [=, &foo]것을 제외하고 모든 변수를 값 으로 캡처하고 값으로 캡처되는 것을 제외하고 모든 변수를 참조 로 캡처합니다. 특정 변수 만 캡처 할 수도 있습니다. 예를 들어 참조로 캡처 하고 다른 변수는 캡처하지 않습니다. 를 사용하여 변수를 전혀 캡처하지 않을 수도 있습니다 . 캡처하지 않은 람다에서 변수를 사용하려고하면 컴파일되지 않습니다. 예를 들면 다음과 같습니다.foo[&, foo]foo[&foo]foo[]

#include <functional>

int main(){
    int x = 4, y = 5;

    std::function<void(int)> myLambda = [y](int z){
        int xSquare = x * x;    //Compiler error because x wasn't captured
        int ySquare = y * y;    //OK because y was captured
        int zSquare = z * z;    //OK because z is an argument of the lambda
    };

    return 0;
}

람다 내부의 값으로 캡처 된 변수의 값을 변경할 수 없습니다 (값으로 캡처 된 변수 const는 람다 내부 에 유형이 있음). 그렇게하려면 참조로 변수를 캡처해야합니다. 다음은 exampmle입니다.

#include <functional>

int main(){
    int x = 3, y = 5;
    std::function<void()> myLambda = [x, &y](){
        x = 2;    //Compiler error because x is captured by value and so it's of type const int inside the lambda
        y = 2;    //OK because y is captured by reference
    };
    x = 2;    //This is of course OK because we're not inside the lambda
    return 0;
}

또한 초기화되지 않은 람다를 호출하는 것은 정의되지 않은 동작이며 일반적으로 프로그램이 중단됩니다. 예를 들어, 절대 이렇게하지 마십시오 :

std::function<void()> lambda;
lambda();    //Undefined behavior because lambda is uninitialized

다음은 람다를 사용하여 질문에서 수행하려는 작업에 대한 코드입니다.

#include <functional>    //Don't forget this, otherwise you won't be able to use the std::function type

int main(){
    std::function<void()> a = [](){
        // code
    }
    a();
    return 0;
}

다음은 람다의 고급 예입니다.

#include <functional>    //For std::function
#include <iostream>      //For std::cout

int main(){
    int x = 4;
    std::function<float(int)> divideByX = [x](int y){
        return (float)y / (float)x;    //x is a captured variable, y is an argument
    }
    std::cout << divideByX(3) << std::endl;    //Prints 0.75
    return 0;
}

7

아니요, 허용되지 않습니다. C 나 C ++ 모두 기본적으로이 기능을 지원하지는 않지만 TonyK는 주석에서 C에서이 동작을 가능하게하는 GNU C 컴파일러의 확장이 있다고 지적합니다.


2
GNU C 컴파일러는 특수 확장명으로 지원합니다. 그러나 C ++이 아닌 C에만 해당됩니다.
TonyK

아 내 C 컴파일러에 특별한 확장이 없습니다. 그래도 좋은 일입니다. 그 대답을 내 대답에 추가하겠습니다.
Thomas Owens

중첩 함수 (C ++이 아닌 C)를 지원하기 위해 gcc 확장을 사용했습니다. 중첩 함수는 일반적으로 사용되지 않는 복잡하면서도 응집력있는 구조를 관리하기에 좋은 기능입니다 (Pascal 및 Ada에서와 같이). 긴 하나로서 사용하는 GCC 툴체인으로,으로 보장 대부분 모든 대상 아키텍처에 이식. 그러나 비 gcc 컴파일러로 결과 코드를 컴파일 해야하는 변경이있는 경우 그러한 확장을 피하고 가능한 한 ansi / posix 만트라에 달라 붙는 것이 가장 좋습니다.
luis.espinal

7

이 모든 트릭은 로컬 함수로 (거의 또는 거의) 보이지만 그렇게 작동하지 않습니다. 로컬 함수에서 수퍼 함수의 로컬 변수를 사용할 수 있습니다. 일종의 반 세계입니다. 이러한 트릭 중 하나도 그렇게 할 수 없습니다. 가장 가까운 것은 c ++ 0x의 람다 트릭이지만 클로저는 사용 시간이 아니라 정의 시간에 바인딩됩니다.


이제 이것이 최선의 대답이라고 생각합니다. 함수 내에서 함수를 선언하는 것이 가능하지만 (항상 사용하는) 다른 언어로 정의 된 로컬 함수는 아닙니다. 가능성을 아는 것이 여전히 좋습니다.
Alexis Wilke


4

가능한 가장 깨끗한 C ++ 03 솔루션을 여기에 게시하겠습니다. *

#define DECLARE_LAMBDA(NAME, RETURN_TYPE, FUNCTION) \
    struct { RETURN_TYPE operator () FUNCTION } NAME;

...

int main(){
  DECLARE_LAMBDA(demoLambda, void, (){ cout<<"I'm a lambda!"<<endl; });
  demoLambda();

  DECLARE_LAMBDA(plus, int, (int i, int j){
    return i+j;
  });
  cout << "plus(1,2)=" << plus(1,2) << endl;
  return 0;
}

매크로를 사용하는 C ++ 세계에서 (*)는 결코 깨끗한 것으로 간주되지 않습니다.


알렉시스, 당신은 그것이 완벽하게 깨끗하지 않다고 말할 권리가 있습니다. 그것은 프로그래머가 부작용없이 부작용을 나타내지 않기 때문에 여전히 깨끗합니다. 저는 프로그래밍 기술이 소설처럼 읽는 사람이 읽을 수있는 표현력을 쓰고 있다고 생각합니다.
Barney

2

그러나 main () 안에 함수를 선언 할 수 있습니다 :

int main()
{
    void a();
}

구문이 맞지만 때때로 "가장 까다로운 구문 분석"으로 이어질 수 있습니다.

#include <iostream>


struct U
{
    U() : val(0) {}
    U(int val) : val(val) {}

    int val;
};

struct V
{
    V(U a, U b)
    {
        std::cout << "V(" << a.val << ", " << b.val << ");\n";
    }
    ~V()
    {
        std::cout << "~V();\n";
    }
};

int main()
{
    int five = 5;
    V v(U(five), U());
}

=> 프로그램 출력이 없습니다.

(컴파일 후 Clang 경고 만).

C ++의 가장 까다로운 구문 분석

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