[c++] 전달 선언은 언제 사용할 수 있습니까?

다른 클래스의 헤더 파일에서 클래스를 전달할 수있는 시점에 대한 정의를 찾고 있습니다.

기본 클래스, 멤버로 보유한 클래스, 참조로 멤버 함수에 전달 된 클래스 등에 대해 수행 할 수 있습니까?



답변

컴파일러의 위치에 자신을 두십시오. 형식을 전달할 때 컴파일러가 알고있는 것은이 형식이 존재한다는 것입니다. 크기, 멤버 또는 방법에 대해서는 아무것도 모릅니다. 이것이 불완전한 유형 이라고 불리는 이유 입니다. 따라서 컴파일러는 형식의 레이아웃을 알아야하기 때문에 형식을 사용하여 멤버 또는 기본 클래스를 선언 할 수 없습니다.

다음과 같은 선언을 가정합니다.

class X;

할 수있는 것과 할 수없는 것이 있습니다.

불완전한 유형으로 할 수있는 작업 :

  • 멤버를 불완전한 유형에 대한 포인터 또는 참조로 선언하십시오.

    class Foo {
        X *p;
        X &r;
    };
    
  • 불완전한 유형을 수락 / 반환하는 함수 또는 메소드를 선언하십시오 .

    void f1(X);
    X    f2();
    
  • 불완전한 유형에 대한 포인터 / 참조를 수락 / 반환하는 함수 또는 메소드를 정의하십시오 (그러나 멤버를 사용하지 않음).

    void f3(X*, X&) {}
    X&   f4()       {}
    X*   f5()       {}
    

불완전한 유형으로는 할 수없는 일 :

  • 기본 클래스로 사용

    class Foo : X {} // compiler error!
  • 이를 사용하여 멤버를 선언하십시오.

    class Foo {
        X m; // compiler error!
    };
    
  • 이 유형을 사용하여 함수 또는 메소드 정의

    void f1(X x) {} // compiler error!
    X    f2()    {} // compiler error!
    
  • 불완전한 유형의 변수를 역 참조하려는 경우 해당 메소드 또는 필드를 사용하십시오.

    class Foo {
        X *m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    

템플릿에 관해서는 절대 규칙이 없습니다. 불완전한 유형을 템플릿 매개 변수로 사용할 수 있는지 여부는 템플릿에서 유형이 사용되는 방식에 따라 다릅니다.

예를 들어, std::vector<T>매개 변수는 완전한 유형이어야하지만 boost::container::vector<T>그렇지는 않습니다. 때로는 특정 멤버 함수를 사용하는 경우에만 완전한 유형이 필요할 수 있습니다. 예를 들어이 경우입니다std::unique_ptr<T> .

잘 문서화 된 템플릿은 완전한 유형이 필요한지 여부를 포함하여 매개 변수의 모든 요구 사항을 문서에 표시해야합니다.


답변

기본 규칙은 메모리 레이아웃 (및 멤버 함수 및 데이터 멤버)을 전달할 파일에서 알 필요가없는 클래스 만 전달 선언 할 수 있다는 것입니다.

이것은 기본 클래스와 참조 및 포인터를 통해 사용되는 클래스 이외의 것을 배제합니다.


답변

Lakos 는 클래스 사용을 구별합니다

  1. in-name-only (정방향 선언으로 충분)
  2. 크기 (클래스 정의가 필요한 경우)

나는 그것이 간결하게 발음되는 것을 본 적이 없다. 🙂


답변

불완전한 유형에 대한 포인터 및 참조뿐만 아니라 불완전한 유형의 매개 변수 및 / 또는 반환 값을 지정하는 함수 프로토 타입을 선언 할 수도 있습니다. 그러나 포인터 또는 참조가 아닌 한 매개 변수 또는 리턴 유형이 불완전한 함수를 정의 할 수 없습니다 .

예 :

struct X;              // Forward declaration of X

void f1(X* px) {}      // Legal: can always use a pointer
void f2(X&  x) {}      // Legal: can always use a reference
X f3(int);             // Legal: return value in function prototype
void f4(X);            // Legal: parameter in function prototype
void f5(X) {}          // ILLEGAL: *definitions* require complete types


답변

지금까지 답변은 클래스 템플릿의 전달 선언을 사용할 수있는 시점을 설명하지 않습니다. 그래서, 여기에 간다.

클래스 템플릿은 다음과 같이 선언하여 전달할 수 있습니다.

template <typename> struct X;

의 구조에 따라 허용 대답 ,

할 수있는 것과 할 수없는 것이 있습니다.

불완전한 유형으로 할 수있는 작업 :

  • 다른 클래스 템플릿에서 멤버를 불완전한 형식에 대한 포인터 또는 참조로 선언합니다.

    template <typename T>
    class Foo {
        X<T>* ptr;
        X<T>& ref;
    };
  • 불완전한 인스턴스화 중 하나에 대한 포인터 또는 참조가되도록 멤버를 선언하십시오.

    class Foo {
        X<int>* ptr;
        X<int>& ref;
    };
  • 불완전한 유형을 수락하거나 반환하는 함수 템플릿 또는 멤버 함수 템플릿을 선언합니다.

    template <typename T>
       void      f1(X<T>);
    template <typename T>
       X<T>    f2();
  • 불완전한 인스턴스화 중 하나를 수락 / 반환하는 함수 또는 멤버 함수를 선언하십시오.

    void      f1(X<int>);
    X<int>    f2();
  • 불완전한 유형에 대한 포인터 / 참조를 수락 / 반환하는 함수 템플리트 또는 멤버 함수 템플리트를 정의하십시오 (하지만 멤버를 사용하지 않음).

    template <typename T>
       void      f3(X<T>*, X<T>&) {}
    template <typename T>
       X<T>&   f4(X<T>& in) { return in; }
    template <typename T>
       X<T>*   f5(X<T>* in) { return in; }
  • 불완전한 인스턴스화 중 하나에 대한 포인터 / 참조를 수락 / 반환하는 함수 또는 메소드를 정의하십시오 (그러나 멤버를 사용하지 않음).

    void      f3(X<int>*, X<int>&) {}
    X<int>&   f4(X<int>& in) { return in; }
    X<int>*   f5(X<int>* in) { return in; }
  • 다른 템플릿 클래스의 기본 클래스로 사용

    template <typename T>
    class Foo : X<T> {} // OK as long as X is defined before
                        // Foo is instantiated.
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
  • 이를 사용하여 다른 클래스 템플릿의 멤버를 선언하십시오.

    template <typename T>
    class Foo {
        X<T> m; // OK as long as X is defined before
                // Foo is instantiated. 
    };
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
  • 이 유형을 사용하여 함수 템플릿 또는 메소드 정의

    template <typename T>
      void    f1(X<T> x) {}    // OK if X is defined before calling f1
    template <typename T>
      X<T>    f2(){return X<T>(); }  // OK if X is defined before calling f2
    
    void test1()
    {
       f1(X<int>());  // Compiler error
       f2<int>();     // Compiler error
    }
    
    template <typename T> struct X {};
    
    void test2()
    {
       f1(X<int>());  // OK since X is defined now
       f2<int>();     // OK since X is defined now
    }

불완전한 유형으로는 할 수없는 일 :

  • 인스턴스화 중 하나를 기본 클래스로 사용

    class Foo : X<int> {} // compiler error!
  • 인스턴스화 중 하나를 사용하여 멤버를 선언하십시오.

    class Foo {
        X<int> m; // compiler error!
    };
  • 인스턴스화 중 하나를 사용하여 함수 또는 메소드 정의

    void      f1(X<int> x) {}            // compiler error!
    X<int>    f2() {return X<int>(); }   // compiler error!
  • 불완전한 유형의 변수를 역 참조하려는 경우 인스턴스화 중 하나의 메소드 또는 필드를 사용하십시오.

    class Foo {
        X<int>* m;
        void method()
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
  • 클래스 템플릿의 명시 적 인스턴스 생성

    template struct X<int>;

답변

클래스에 대한 포인터 또는 참조 만 사용하는 파일에서 포인터 / 참조를 생각하면 멤버 / 멤버 함수를 호출해서는 안됩니다.

class Foo;// 앞으로 선언

Foo * 또는 Foo & 유형의 데이터 멤버를 선언 할 수 있습니다.

Foo 유형의 인수 및 / 또는 반환 값으로 함수를 선언 할 수 있지만 정의 할 수는 없습니다.

Foo 타입의 정적 데이터 멤버를 선언 할 수 있습니다. 정적 데이터 멤버는 클래스 정의 외부에 정의되어 있기 때문입니다.


답변

합법성에 근거한 것이 아니라 강력한 소프트웨어 및 잘못된 해석의 위험에 대한 Luc Touraille의 답변에 동의하지 않기 때문에 이것을 주석이 아닌 별도의 답변으로 작성하고 있습니다.

특히 인터페이스 사용자가 알아야 할 내용에 대한 묵시적 계약에 문제가 있습니다.

참조 유형을 반환하거나 수락하는 경우 포인터 또는 참조를 전달할 수 있으며 순방향 선언을 통해서만 알 수 있습니다.

불완전한 유형 X f2();을 반환하는 경우 호출자 X의 전체 유형 사양을 가져야 한다고 말하고 있습니다. 호출 사이트에서 LHS 또는 임시 개체를 만들려면 필요합니다.

마찬가지로, 불완전한 유형을 허용하면 호출자는 매개 변수 인 오브젝트를 구성해야합니다. 해당 객체가 함수에서 다른 불완전한 유형으로 반환 되더라도 호출 사이트에는 전체 선언이 필요합니다. 즉 :

class X;  // forward for two legal declarations 
X returnsX();
void XAcceptor(X);

XAcepptor( returnsX() );  // X declaration needs to be known here

헤더가 다른 헤더를 필요로하는 종속성없이 사용하기에 충분한 정보를 제공해야한다는 중요한 원칙이 있다고 생각합니다. 즉, 선언하는 함수를 사용할 때 컴파일러 오류를 발생시키지 않고 헤더를 컴파일 단위에 포함 할 수 있어야합니다.

  1. 이 외부 의존성이 필요한 경우 행동. 조건부 컴파일을 사용하는 대신 X를 선언하는 자체 헤더를 제공하기 위해 잘 문서화 된 요구 사항을 가질 수 있습니다. 이것은 #ifdefs를 사용하는 대안이며 모의 품 또는 기타 변형을 도입하는 유용한 방법이 될 수 있습니다.

  2. 중요한 차이점은 명시 적으로 인스턴스화 할 것으로 예상되지 않는 템플릿 기술이며 누군가가 나에게 소리를 지르지 않도록 언급했습니다.