C ++에서 함수 내에서 구조와 클래스를 정의 할 수있는 이유는 무엇입니까?


92

C ++에서 실수로 이와 같은 작업을 수행했으며 작동합니다. 왜 이것을 할 수 있습니까?

int main(int argc, char** argv) {
    struct MyStruct
    {
      int somevalue;
    };

    MyStruct s;
    s.somevalue = 5;
}

이 일을 한 후, 나는 오래 전에 C ++를위한 가난한 사람의 함수형 프로그래밍 도구로이 트릭에 대해 읽었던 기억이 났지만 이것이 왜 유효한지, 어디서 읽었는지 기억할 수 없습니다.

두 질문에 대한 답변을 환영합니다!

참고 : 질문을 작성할 때이 질문에 대한 참조를 얻지 못했지만 현재 사이드 바에서이를 지적하므로 질문이 다르지만 유용 할 수 있습니다.


답변:


72

[2013 년 4 월 18 일 편집] : 다행히 C ++ 11에서는 아래에 언급 된 제한이 해제되었으므로 로컬에서 정의 된 클래스가 유용합니다! 댓글 작성자 bamboon에게 감사드립니다.

클래스를 로컬로 정의하는 기능 사용자 정의 펑 (AN와 클래스 생성하기 operator()()에 전달하는, 예를 들어 비교 기능을std::sort() 함께 사용할 수 또는 "루프 기관" std::for_each()) 훨씬 더 편리.

불행히도 C ++는 템플릿과 함께 로컬 정의 클래스를 사용하는 것을 금지합니다. 는 링크가 없기 때문에 합니다. 대부분의 펑터 애플리케이션은 펑터 유형에 템플릿 화 된 템플릿 유형을 포함하기 때문에 로컬로 정의 된 클래스는이를 위해 사용할 수 없습니다. 함수 외부에서 정의해야합니다. :(

[2009 년 1 월 1 일 수정]

표준의 관련 견적은 다음과 같습니다.

14.3.1 / 2 : . 로컬 유형, 연결이없는 유형, 명명되지 않은 유형 또는 이러한 유형의 복합 유형은 템플릿 유형 매개 변수에 대한 템플릿 인수로 사용되지 않습니다.


2
경험적으로 이것은 MSVC ++ 8에서 작동하는 것 같습니다. (하지만 g ++는 아닙니다.)
j_random_hacker

나는 gcc 4.3.3을 사용하고 있으며 거기에서 작동하는 것 같습니다 : pastebin.com/f65b876b . 표준에서 금지하는 부분에 대한 언급이 있습니까? 사용시 쉽게 인스턴스화 할 수있는 것 같습니다.
Catskul

@Catskul : 14.3.1 / 2 : "지역 유형, 연결이없는 유형, 명명되지 않은 유형 또는 이러한 유형의 복합 유형은 템플릿 유형 매개 변수에 대한 템플릿 인수로 사용되지 않습니다." 근거가되는 이유는 로컬 클래스가 망가진 이름으로 밀어 넣으려면 또 다른 정보를 필요로한다는 것입니다.하지만 확실히 모르겠습니다. 물론 특정 컴파일러는 MSVC ++ 8 및 최신 버전의 g ++에서 제공하는 것처럼이 문제를 해결하기 위해 확장을 제공 할 수 있습니다.
j_random_hacker

9
이 제한은 C ++ 11에서 해제되었습니다.
Stephan Dollberg 2013

31

로컬로 정의 된 C ++ 클래스의 한 응용 프로그램은 Factory 디자인 패턴입니다 .


// In some header
class Base
{
public:
    virtual ~Base() {}
    virtual void DoStuff() = 0;
};

Base* CreateBase( const Param& );

// in some .cpp file
Base* CreateBase( const Params& p )
{
    struct Impl: Base
    {
        virtual void DoStuff() { ... }
    };

    ...
    return new Impl;
}

익명 네임 스페이스로도 똑같이 할 수 있습니다.


흥미 롭군요! 내가 언급 한 템플릿에 관한 제한 사항이 적용되지만,이 접근 방식은 CreateBase ()를 제외하고는 Impl의 인스턴스를 만들 수 (또는 이야기 할 수도 있습니다!) 보장합니다. 따라서 이것은 클라이언트가 구현 세부 사항에 의존하는 정도를 줄이는 훌륭한 방법으로 보입니다. +1.
j_random_hacker

26
그것은 깔끔한 아이디어입니다. 조만간 사용할 것인지는 확실하지 않지만, 바에서 일부 병아리에게 깊은 인상을 남기는 것이 좋습니다. :)
Robert Gould

2
(나는 대답이 아니라 병아리 BTW에 대해 이야기하고 있습니다!)
markh44

9
lol Robert ... 그래, C ++의 모호한 모서리에 대해 아는 것만 큼 여자에게 깊은 인상을주는 것은 없습니다 ...
j_random_hacker

10

실제로 스택 기반 예외 안전 작업을 수행하는 데 매우 유용합니다. 또는 여러 반환 지점이있는 함수에서 일반적인 정리. 이를 종종 RAII (자원 획득은 초기화) 관용구라고합니다.

void function()
{

    struct Cleaner
    {
        Cleaner()
        {
            // do some initialization code in here
            // maybe start some transaction, or acquire a mutex or something
        }

        ~Cleaner()
        {
             // do the associated cleanup
             // (commit your transaction, release your mutex, etc.)
        }
    };

    Cleaner cleaner;

    // Now do something really dangerous
    // But you know that even in the case of an uncaught exception, 
    // ~Cleaner will be called.

    // Or alternatively, write some ill-advised code with multiple return points here.
    // No matter where you return from the function ~Cleaner will be called.
}

5
Cleaner cleaner();나는 이것이 객체 정의가 아닌 함수 선언이 될 것이라고 생각합니다.
사용자

2
@user 맞습니다. 기본 생성자를 호출하기 위해, 그는 작성해야 Cleaner cleaner;하거나 Cleaner cleaner{};.
callyalater apr

함수 내부의 클래스는 RAII와 관련이 없으며 유효한 C ++ 코드가 아니며 컴파일되지 않습니다.
Mikhail Vasilyev

1
심지어 함수 내부에서이 같은 클래스는 정확히 무엇 ++ C에서 RAII 것은 모두에 관한 것입니다.
Christopher Bruns

9

글쎄요, 기본적으로 왜 안되죠? ㅏstruct in C (시간의 새벽으로 돌아 가기)는 레코드 구조를 선언하는 방법 일뿐입니다. 원하는 경우 간단한 변수를 선언 할 위치에서 선언 할 수없는 이유는 무엇입니까?

일단 그렇게하면 C ++의 목표는 가능하다면 C와 호환되는 것이 었습니다. 그래서 그것은 남았습니다.


살아남을 수있는 깔끔한 기능이지만 j_random_hacker가 방금 지적했듯이 C ++에서 상상했던 것만 큼 유용하지 않습니다. : /
Robert Gould

예, C에서도 범위 지정 규칙이 이상했습니다. 이제 저는 C ++에 대해 25 년 이상의 경험을 쌓았으므로 C와 비슷하게되기 위해 노력하는 것은 실수 일 수 있습니다. 반면에 에펠과 같은 더 우아한 언어는 거의 쉽게 채택되지 않았습니다.
Charlie Martin

예, 기존 C 코드베이스를 C ++로 마이그레이션했습니다 (Eiffel은 아님).
ChrisW


3

제대로 초기화 된 개체의 배열을 만들기위한 것입니다.

기본 생성자가없는 클래스 C가 있습니다. 클래스 C의 개체 배열을 원합니다. 이러한 개체를 초기화하는 방법을 파악한 다음 D의 기본 생성자에서 C에 대한 인수를 제공하는 정적 메서드를 사용하여 C에서 클래스 D를 파생합니다.

#include <iostream>
using namespace std;

class C {
public:
  C(int x) : mData(x)  {}
  int method() { return mData; }
  // ...
private:
  int mData;
};

void f() {

  // Here I am in f.  I need an array of 50 C objects starting with C(22)

  class D : public C {
  public:
    D() : C(D::clicker()) {}
  private:
    // I want my C objects to be initialized with consecutive
    // integers, starting at 22.
    static int clicker() { 
      static int current = 22;
      return current++;
    } 
  };

  D array[50] ;

  // Now I will display the object in position 11 to verify it got initialized
  // with the right value.  

  cout << "This should be 33: --> " << array[11].method() << endl;

  cout << "sizodf(C): " << sizeof(C) << endl;
  cout << "sizeof(D): " << sizeof(D) << endl;

  return;

}

int main(int, char **) {
  f();
  return 0;
}

간단하게하기 위해이 예제에서는 기본이 아닌 간단한 생성자와 컴파일 시간에 값이 알려진 경우를 사용합니다. 런타임에만 알려진 값으로 초기화 된 객체 배열을 원하는 경우에이 기술을 확장하는 것은 간단합니다.


확실히 흥미로운 응용 프로그램입니다! 그것이 현명하거나 안전한지 확실하지 않습니다. D의 배열을 C의 배열로 취급해야하는 경우 (예 : D*매개 변수 를받는 함수에 전달해야 함 ) D가 실제로 C보다 크면 조용히 중단됩니다. . (나는 ... 생각)
j_random_hacker

+ j_random_hacker, sizeof (D) == sizeof (C). 나는 당신을 위해 sizeof () 보고서를 추가했습니다.
Thomas L Holaday 2013
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.