클래스 선언이 다른 클래스를 포인터로만 사용할 때마다 순환 종속성 문제를 선제 적으로 방지하기 위해 헤더 파일을 포함하는 대신 클래스 전달 선언을 사용하는 것이 합리적입니까? 그래서 대신 :
//file C.h
#include "A.h"
#include "B.h"
class C{
A* a;
B b;
...
};
대신 다음을 수행하십시오.
//file C.h
#include "B.h"
class A;
class C{
A* a;
B b;
...
};
//file C.cpp
#include "C.h"
#include "A.h"
...
가능할 때마다 이것을하지 않는 이유가 있습니까?
답변
앞으로 선언하는 방법은 거의 항상 더 좋습니다. (정방향 선언을 사용할 수있는 파일을 포함하는 것이 더 좋은 상황은 생각할 수 없지만 혹시라도 항상 더 좋다고 말할 수는 없습니다).
포워드 선언 클래스에는 단점이 없지만 불필요하게 헤더를 포함 할 경우 몇 가지 단점을 생각할 수 있습니다.
-
더 이상 컴파일 시간, 모든 번역 단위를 포함하여 이후
C.h
도 포함됩니다A.h
그들이 필요하지 않을 수도 있지만. -
간접적으로 필요하지 않은 다른 헤더를 포함 할 수 있습니다.
-
필요하지 않은 기호로 번역 단위를 오염시킵니다.
-
헤더가 변경되면 해당 헤더를 포함하는 소스 파일을 다시 컴파일해야 할 수 있습니다 (@PeterWood)
답변
예, 앞으로 선언을 사용하는 것이 항상 더 좋습니다.
그들이 제공하는 몇 가지 장점은 다음과 같습니다.
- 컴파일 시간 단축.
- 네임 스페이스 오염이 없습니다.
- (경우에 따라) 생성 된 바이너리의 크기를 줄일 수 있습니다.
- 재 컴파일 시간을 크게 줄일 수 있습니다.
- 전 처리기 이름의 잠재적 충돌 방지.
- 구현 PIMPL 관용구 따라서 인터페이스 구현을 은폐하는 수단을 제공한다.
그러나 클래스를 Forward로 선언하면 특정 클래스가 Incomplete 유형이 되고 이는 Incomplete 유형에서 수행 할 수있는 작업을 심각하게 제한합니다.
클래스의 레이아웃을 알기 위해 컴파일러가 필요한 작업을 수행 할 수 없습니다.
불완전한 유형으로 다음을 수행 할 수 있습니다.
- 멤버를 불완전한 유형에 대한 포인터 또는 참조로 선언하십시오.
- 불완전한 유형을 허용 / 반환하는 함수 또는 메소드를 선언하십시오.
- 불완전한 유형에 대한 포인터 / 참조를 수락 / 반환하는 함수 또는 메서드를 정의합니다 (하지만 멤버를 사용하지 않음).
불완전한 유형으로 다음을 수행 할 수 없습니다.
- 기본 클래스로 사용하십시오.
- 이를 사용하여 구성원을 선언하십시오.
- 이 유형을 사용하여 함수 또는 방법을 정의하십시오.
답변
가능할 때마다 이것을하지 않는 이유가 있습니까?
편의.
이 헤더 파일의 사용자 A
가 무엇이든 (또는 대부분의 경우) 수행 할 정의를 반드시 포함해야한다는 것을 미리 알고 있다면 . 그런 다음 한 번만 포함하는 것이 편리합니다.
이 엄지 손가락 규칙을 너무 자유로이 사용하면 거의 컴파일 할 수없는 코드가 생성되기 때문에 이것은 다소 민감한 주제입니다. Boost는 몇 가지 밀접한 기능을 함께 묶는 특정 “편의”헤더를 제공함으로써 문제에 다르게 접근합니다.
답변
포워드 선언을 원하지 않는 한 가지 경우는 그 자체가 까다로울 때입니다. 다음 예제와 같이 일부 클래스가 템플릿 화 된 경우 이러한 상황이 발생할 수 있습니다.
// Forward declarations
template <typename A> class Frobnicator;
template <typename A, typename B, typename C = Frobnicator<A> > class Gibberer;
// Alternative: more clear to the reader; more stable code
#include "Gibberer.h"
// Declare a function that does something with a pointer
int do_stuff(Gibberer<int, float>*);
순방향 선언은 코드 복제와 동일합니다. 코드가 많이 변경되는 경향이있는 경우 매번 2 곳 이상에서 변경해야하며 이는 좋지 않습니다.
답변
재미있는 사실 은 C ++ 스타일 가이드 에서 Google은 #include
순환 종속성을 피하기 위해 모든 곳에서 사용 하도록 권장 합니다.
답변
가능한 한 include 대신 포워드 선언을 사용해야합니까?
아니요, 명시적인 전방 선언은 일반적인 지침으로 간주되어서는 안됩니다. 포워드 선언은 본질적으로 복사하여 붙여 넣거나 철자가 틀린 코드로, 버그를 발견 한 경우 포워드 선언이 사용되는 모든 곳에서 수정해야합니다. 이는 오류가 발생하기 쉽습니다.
“앞으로”선언과 해당 정의 사이의 불일치를 방지하려면 선언을 헤더 파일에 넣고 해당 헤더 파일을 정의 및 선언 사용 소스 파일 모두에 포함합니다.
그러나 불투명 한 클래스 만 포워드 선언되는이 특별한 경우에는이 포워드 선언을 사용해도 괜찮지 만, 일반적으로이 스레드의 제목처럼 “가능하면 항상 포함 대신 포워드 선언을 사용”할 수 있습니다. 꽤 위험합니다.
다음은 전방 선언과 관련된 “보이지 않는 위험”의 몇 가지 예입니다 (보이지 않는 위험 = 컴파일러 또는 링커에서 감지하지 않는 선언 불일치).
-
데이터를 나타내는 기호의 명시 적 전방 선언은 안전하지 않을 수 있습니다. 이러한 전방 선언에는 데이터 유형의 풋 프린트 (크기)에 대한 정확한 지식이 필요할 수 있기 때문입니다.
-
함수를 나타내는 기호의 명시 적 전방 선언도 매개 변수 유형 및 매개 변수 수와 같이 안전하지 않을 수 있습니다.
아래의 예는이를 보여줍니다. 예를 들어, 데이터와 함수의 두 가지 위험한 전방 선언이 있습니다.
파일 ac :
#include <iostream>
char data[128][1024];
extern "C" void function(short truncated, const char* forgotten) {
std::cout << "truncated=" << std::hex << truncated
<< ", forgotten=\"" << forgotten << "\"\n";
}
파일 bc :
#include <iostream>
extern char data[1280][1024]; // 1st dimension one decade too large
extern "C" void function(int tooLarge); // Wrong 1st type, omitted 2nd param
int main() {
function(0x1234abcd); // In worst case: - No crash!
std::cout << "accessing data[1270][1023]\n";
return (int) data[1270][1023]; // In best case: - Boom !!!!
}
g ++ 4.7.1로 프로그램 컴파일 :
> g++ -Wall -pedantic -ansi a.c b.c
참고 : g ++는 컴파일러 또는 링커 오류 / 경고를 제공하지 않으므로 보이지 않는 위험입니다.
참고 : 생략 하면 C ++ 이름 변경으로 인해 extern "C"
연결 오류가 function()
발생합니다.
프로그램 실행 :
> ./a.out
truncated=abcd, forgotten="♀♥♂☺☻"
accessing data[1270][1023]
Segmentation fault
답변
가능할 때마다 이것을하지 않는 이유가 있습니까?
절대적으로 : 클래스 또는 함수의 사용자가 구현 세부 정보를 알고 복제하도록 요구하여 캡슐화를 중단합니다. 이러한 구현 세부 정보가 변경되면 앞으로 선언하는 코드가 손상 될 수 있지만 헤더에 의존하는 코드는 계속 작동합니다.
함수 선언 :
-
정적 펑터 객체 또는 매크로가 아닌 함수로 구현되었음을 알아야합니다.
-
기본 매개 변수의 기본값을 복제해야합니다.
-
실제 이름과 네임 스페이스를 알아야합니다. 왜냐하면
using
별칭 아래에서 다른 네임 스페이스로 가져 오는 선언 일 수도 있기 때문입니다. -
인라인 최적화를 잃을 수 있습니다.
소비 코드가 헤더에 의존하는 경우, 이러한 모든 구현 세부 사항은 코드를 손상시키지 않고 함수 공급자에 의해 변경 될 수 있습니다.
클래스를 포워드 선언 :
-
파생 클래스인지 그리고 파생 된 기본 클래스인지 알아야합니다.
-
typedef 또는 클래스 템플릿의 특정 인스턴스화가 아닌 클래스임을 알아야합니다 (또는 클래스 템플릿임을 알고 모든 템플릿 매개 변수와 기본값을 올바르게 가져옴).
-
클래스의 실제 이름과 네임 스페이스를 알아야합니다. 이는
using
아마도 별칭 아래에있는 다른 네임 스페이스로 가져 오는 선언 일 수 있기 때문입니다. -
올바른 속성을 알아야합니다 (아마도 특별한 정렬 요구 사항이있을 수 있습니다).
다시 말하지만, 포워드 선언은 이러한 구현 세부 정보의 캡슐화를 해제하여 코드를 더 취약하게 만듭니다.
컴파일 시간을 단축하기 위해 헤더 종속성을 잘라 내야하는 경우 클래스 / 함수 / 라이브러리 공급자에게 특수 정방향 선언 헤더를 제공하도록 요청하세요. 표준 라이브러리는 <iosfwd>
. 이 모델은 구현 세부 사항의 캡슐화를 유지하고 라이브러리 관리자에게 코드를 손상시키지 않고 이러한 구현 세부 사항을 변경할 수있는 기능을 제공하는 동시에 컴파일러의 부하를 줄입니다.
또 다른 옵션은 구현 세부 사항을 훨씬 더 잘 숨기고 작은 런타임 오버 헤드로 컴파일 속도를 높이는 pimpl 관용구를 사용하는 것입니다.