다른 클래스의 헤더 파일에서 클래스를 전달할 수있는 시점에 대한 정의를 찾고 있습니다.
기본 클래스, 멤버로 보유한 클래스, 참조로 멤버 함수에 전달 된 클래스 등에 대해 수행 할 수 있습니까?
다른 클래스의 헤더 파일에서 클래스를 전달할 수있는 시점에 대한 정의를 찾고 있습니다.
기본 클래스, 멤버로 보유한 클래스, 참조로 멤버 함수에 전달 된 클래스 등에 대해 수행 할 수 있습니까?
답변:
컴파일러의 위치에 자신을 두십시오. 형식을 전달할 때 컴파일러가 알고있는 것은이 형식이 존재한다는 것입니다. 크기, 멤버 또는 방법에 대해서는 아무것도 모릅니다. 이것이 불완전한 유형 이라고 불리는 이유 입니다. 따라서 컴파일러는 형식의 레이아웃을 알아야하기 때문에 형식을 사용하여 멤버 또는 기본 클래스를 선언 할 수 없습니다.
다음과 같은 선언을 가정합니다.
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 는 클래스 사용을 구별합니다
나는 그것이 간결하게 발음되는 것을 본 적이 없다. :)
불완전한 유형에 대한 포인터 및 참조뿐만 아니라 불완전한 유형의 매개 변수 및 / 또는 반환 값을 지정하는 함수 프로토 타입을 선언 할 수도 있습니다. 그러나 포인터 또는 참조가 아닌 한 매개 변수 또는 리턴 유형이 불완전한 함수를 정의 할 수 없습니다 .
예 :
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>;
X
와 X<int>
동일하며, 모두 어떤 실질적인 방법으로 단지 앞으로 선언 구문 다르다, 그러나 다만 루크의과 복용에 대한 당신의 대답에 상당한다 1 개 라인 s/X/X<int>/g
? 정말 필요한가요? 아니면 다른 작은 세부 사항을 놓쳤습니까? 가능하지만 시각적으로 몇 번 비교했지만 아무것도 볼 수 없습니다.
합법성에 근거한 것이 아니라 강력한 소프트웨어 및 잘못된 해석의 위험에 대한 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
헤더가 다른 헤더를 필요로하는 종속성없이 사용하기에 충분한 정보를 제공해야한다는 중요한 원칙이 있다고 생각합니다. 즉, 선언하는 함수를 사용할 때 컴파일러 오류를 발생시키지 않고 헤더를 컴파일 단위에 포함 할 수 있어야합니다.
외
이 외부 의존성이 필요한 경우 행동. 조건부 컴파일을 사용하는 대신 X를 선언하는 자체 헤더를 제공하기 위해 잘 문서화 된 요구 사항을 가질 수 있습니다. 이것은 #ifdefs를 사용하는 대안이며 모의 품 또는 기타 변형을 도입하는 유용한 방법이 될 수 있습니다.
중요한 차이점은 명시 적으로 인스턴스화 할 것으로 예상되지 않는 템플릿 기술이며 누군가가 나에게 소리를 지르지 않도록 언급했습니다.
I disagree with Luc Touraille's answer
따라서 길이가 필요한 경우 블로그 게시물에 대한 링크를 포함하여 의견을 작성하십시오. 이 질문에 대답하지 않습니다. 모든 사람들이 X가 어떻게 작동하는지에 대한 질문을 X가 X를 사용하는 자유를 제한해야하는 토론이나 한계에 동의하지 않는 경우, 답변이 거의 없습니다.
내가 따르는 일반적인 규칙은 내가하지 않는 한 헤더 파일을 포함시키지 않는 것입니다. 따라서 클래스의 객체를 클래스의 멤버 변수로 저장하지 않으면 포함시키지 않고 앞으로 선언을 사용합니다.
일반적으로 다른 유형 (클래스)을 클래스의 멤버로 사용하려는 경우 클래스 헤더 파일에서 정방향 선언을 사용하려고합니다. C ++은 그 시점에서 해당 클래스의 정의를 아직 알지 못하므로 헤더 파일에서 앞으로 선언 된 클래스 메소드 를 사용할 수 없습니다 . 그것이 .cpp 파일로 옮겨야하는 논리이지만, 템플릿 함수를 사용하는 경우 템플릿을 사용하는 부분으로 축소하고 해당 함수를 헤더로 옮겨야합니다.
앞으로 선언하면 코드를 컴파일 할 수 있습니다 (obj가 생성됨). 그러나 정의를 찾을 수 없으면 연결 (exe 작성)에 실패합니다.
class A; class B { A a; }; int main(){}
, 그것이 어떻게되는지 알려주십시오. 물론 컴파일되지 않습니다. 여기에있는 모든 정답은 왜 앞으로 선언 이 유효한지 , 정확하고 제한된 상황을 설명 합니다. 대신 완전히 다른 것에 대해 이것을 작성했습니다.
Luc Touraille의 답변에 언급되지 않은 전달 클래스로 할 수있는 중요한 일을 하나 추가하고 싶습니다.
불완전한 유형으로 할 수있는 작업 :
불완전한 유형에 대한 포인터 / 참조를 수락 / 반환하고 해당 포인터 / 참조를 다른 함수에 전달하는 함수 또는 메소드를 정의하십시오 .
void f6(X*) {}
void f7(X&) {}
void f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }
모듈은 정방향 선언 된 클래스의 객체를 다른 모듈로 전달할 수 있습니다.
마찬가지로 Luc Touraille은 클래스의 앞으로 선언을 사용하고 사용하지 않는 곳을 이미 잘 설명했습니다.
나는 우리가 그것을 사용해야하는 이유를 덧붙일 것입니다.
원치 않는 의존성 주입을 피하기 위해 가능하면 Forward 선언을 사용해야합니다.
마찬가지로 #include
헤더 파일 따라서 복수의 파일에 추가된다 우리가 다른 헤더 파일에 헤더를 추가 할 경우, 추가로 방지 할 수있는 소스 코드의 다양한 부분에서 불필요한 의존성 주입을 추가 할 것이다 #include
에 헤더 .cpp
파일 가능한보다는 다른 헤더 파일에 추가하고 헤더 .h
파일 에서 클래스 포워드 선언을 사용 하십시오.