전 이적으로 포함 된 헤더에 의존하는 것이 좋습니다?


37

작업중 인 C ++ 프로젝트에서 포함을 정리하고 있으며 특정 파일에 직접 사용되는 모든 헤더를 명시 적으로 포함 해야하는지 또는 최소값 만 포함 해야하는지 궁금합니다.

예를 들면 다음과 같습니다 Entity.hpp.

#include "RenderObject.hpp"
#include "Texture.hpp"

struct Entity {
    Texture texture;
    RenderObject render();
}

(forward 선언 RenderObject은 옵션이 아니라고 가정합니다 .)

지금, 나는 그 아는 RenderObject.hpp포함 Texture.hpp- 나는 각이 때문에 알고 RenderObjectTexture멤버. 여전히에 포함 Texture.hpp되어 Entity.hpp있는지에 대한 확신이 있는지 확실하지 않기 때문에에 명시 적 으로 포함하고 RenderObject.hpp있습니다.

그래서 : 좋은 습관입니까?


19
귀하의 예에서 포함 가드는 어디에 있습니까? 실수로 잊어 버렸어요.
Doc Brown

3
사용 된 모든 파일을 포함하지 않을 때 발생하는 한 가지 문제는 때로는 파일을 포함하는 순서가 중요하다는 것입니다. 그것은 발생하는 단 하나의 경우에 정말 성가신 일이지만 때로는 눈덩이와 같은 코드를 작성한 사람이 발사 대원 앞에서 행진하기를 정말로 원합니다.
덩크

이것이 이유 #ifndef _RENDER_H #define _RENDER_H ... #endif입니다.
sampathsris 2018

@ 덩크 나는 당신이 문제를 오해했다고 생각합니다. 그의 제안 중 하나가 발생해서는 안됩니다.
Mooing Duck

1
@DocBrown, 안 돼요 #pragma once?
Pacerier

답변:


65

해당 파일의 내용에 대해 알고있는 것과 상관없이 해당 파일의 .cpp 파일에 사용 된 개체를 정의하는 모든 헤더를 항상 포함해야합니다. 헤더를 여러 번 포함하는 것이 중요하지 않도록 모든 헤더 파일에 가드를 포함해야합니다.

그 원인:

  • 이를 통해 해당 소스 파일이 요구하는 것과 정확하게 소스를 읽는 개발자에게 명확 해집니다. 여기에서 파일의 처음 몇 줄을보고있는 누군가 Texture가이 파일의 객체를 다루고 있음을 알 수 있습니다 .
  • 따라서 리팩터링 된 헤더가 더 이상 특정 헤더 자체가 필요하지 않을 때 컴파일 문제를 일으키는 문제를 피할 수 있습니다. 예를 들어 RenderObject.hpp실제로 필요하지 않다는 것을 알고 있다고 가정하십시오 Texture.hpp.

결과적으로 해당 파일에 명시 적으로 필요한 경우가 아니면 다른 헤더에 헤더를 포함해서는 안됩니다.


10
계약에 동의하십시오-단, 필요한 경우 다른 헤더를 항상 포함시켜야합니다!
앤드류

1
모든 개별 클래스에 헤더를 직접 포함하는 것을 싫어합니다. 누적 헤더를 선호합니다. 즉, 상위 레벨 파일은 사용중인 일종의 "모듈"을 참조해야한다고 생각하지만 모든 개별 부품을 직접 포함 할 필요는 없습니다.
edA-qa mort-ora-y

8
그 결과 모든 파일에 포함 된 크고 모 놀리 식 헤더가 생겨 약간의 비트 만 있으면 컴파일 시간이 길어지고 리팩토링이 더 어려워집니다.
로봇 고트

6
Google은 include-what-you-use 라고하는 이러한 조언을 정확하게 시행 할 수있는 도구를 만들었습니다 .
Matthew G.

3
큰 모 놀리 식 헤더의 주요 컴파일 시간 문제는 헤더 코드 자체를 컴파일 할 때가 아니라 헤더가 변경 될 때마다 응용 프로그램의 모든 cpp 파일을 컴파일해야한다는 것입니다. 미리 컴파일 된 헤더는 도움이되지 않습니다.
로봇

23

일반적으로 사용하는 내용을 포함하십시오. 객체를 직접 사용하는 경우 헤더 파일을 직접 포함하십시오. B를 사용하지만 B를 직접 사용하지 않는 객체 A를 사용하는 경우 Ah 만 포함하십시오.

또한 주제에 관한 동안 실제로 헤더에 필요한 경우 헤더 파일에 다른 헤더 파일 만 포함해야합니다. .cpp에만 필요하면 여기에만 포함하십시오. 이것은 공개 및 개인 종속성의 차이점이며 클래스 사용자가 실제로 필요하지 않은 헤더를 드래그하지 못하게합니다.


10

특정 파일에 직접 사용 된 모든 헤더를 명시 적으로 포함 해야하는지 궁금합니다.

예.

다른 헤더가 언제 변경 될지는 알 수 없습니다. 각 번역 단위에 번역 단위에 필요한 헤더를 포함시키는 것이 전 세계적으로 의미가 있습니다.

이중 포함이 유해하지 않도록 헤더 보호대가 있습니다.


3

이것에 대한 의견은 다르지만 모든 파일 (c / cpp 소스 파일 또는 h / hpp 헤더 파일)은 자체적으로 컴파일하거나 분석 할 수 있어야한다는 견해입니다.

따라서 모든 파일은 필요한 모든 헤더 파일을 #include해야합니다. 하나의 헤더 파일이 이미 포함되어 있다고 가정해서는 안됩니다.

헤더 파일을 추가하고 직접 포함하지 않고 다른 곳에 정의 된 항목을 사용하는 것을 발견하면 정말 고통 스럽습니다. 그래서 찾아야합니다 (아마도 잘못된 것으로 끝납니다!)

반대로, 필요하지 않은 파일을 #include하면 (일반적으로) 문제가되지 않습니다 ...


개인 스타일의 포인트로, #include 파일을 알파벳 순서로 정렬하고 시스템과 응용 프로그램으로 나눕니다. 이는 "자체가 포함되고 완전히 일관된"메시지를 강화하는 데 도움이됩니다.


포함 순서에 대한 참고 사항 : X11 헤더를 포함하는 경우와 같이 순서가 중요한 경우가 있습니다. 이것은 디자인 때문일 수 있으며 (이 경우 잘못된 디자인으로 간주 될 수 있음) 때로는 불행한 비 호환성 문제 때문일 수 있습니다.
하이드

불필요한 헤더를 포함하는 것에 대한 참고 사항은 컴파일 시간과 관련이 있습니다 (특히 템플릿이 많은 C ++ 인 경우). 그러나 특히 포함 파일도 변경되는 동일하거나 종속성 프로젝트의 헤더를 포함하고 다시 컴파일을 트리거 할 때 그것을 포함하여 모든 것 (작업 종속성이있는 경우, 그렇지 않으면 항상 깨끗한 빌드를 수행해야합니다 ...).
하이드

2

전 이적 포함이 필요한지 (예 : 기본 클래스) 또는 구현 세부 사항 (개인 멤버)에 의한 것인지에 따라 다릅니다.

명확히하기 위해 전 이적 포함은 제거 할 때 필요합니다. 중간 헤더에 선언 된 인터페이스를 먼저 변경 한 후에 만 ​​수행 할 수 있습니다. 그것은 이미 중대한 변화이므로, 그것을 사용하는 .cpp 파일은 어쨌든 검사해야합니다.

예 : Ah는 C.cpp에서 사용되는 Bh에 포함됩니다. Bh가 구현 세부 사항으로 Ah를 사용한 경우 C.cpp는 Bh가 계속 그렇게 할 것이라고 가정해서는 안됩니다. 그러나 Bh가 기본 클래스에 Ah를 사용하는 경우 C.cpp는 Bh가 기본 클래스에 대한 관련 헤더를 계속 포함한다고 가정 할 수 있습니다.

여기에 헤더 포함을 복제하지 않는 장점이 있습니다. Bh가 사용하는 기본 클래스가 실제로 Ah에 속하지 않고 Bh 자체로 리팩토링되었다고 가정하십시오. Bh는 이제 독립형 헤더입니다. C.cpp에 Ah가 중복 포함 된 경우 이제 불필요한 헤더가 포함됩니다.


2

또 다른 경우가있을 수 있습니다. Ah, Bh 및 C.cpp가 있으며 Bh에는 Ah가 포함됩니다

C.cpp에서는

#include "B.h"
#include "A.h" // < this can be optional as B.h already has all the stuff in A.h

여기에 #include "Ah"를 쓰지 않으면 어떻게 될까요? C.cpp에서는 A와 B (예 : 클래스)가 모두 사용됩니다. 나중에 C.cpp 코드를 변경하고 B 관련 항목을 제거했지만 Bh는 포함시키지 않았습니다.

Ah와 Bh를 모두 포함하고 지금이 시점에서 불필요한 포함을 감지하는 도구를 사용하면 Bh 포함이 더 이상 필요하지 않음을 알 수 있습니다. 위와 같이 Bh 만 포함하면 도구 / 사람이 코드 변경 후 불필요한 포함을 감지하기 어렵습니다.


1

제안 된 답변과 약간 다른 접근 방식을 취하고 있습니다.

헤더에는 항상 최소값 만 포함하고 컴파일 패스를 만드는 데 필요한 것만 포함하십시오. 가능하면 앞으로 선언을 사용하십시오.

소스 파일에서 얼마나 많이 포함하는지는 중요하지 않습니다. 내 환경 설정에는 여전히 최소값이 포함되어야합니다.

여기에 헤더를 포함하여 작은 프로젝트의 경우 차이가 없습니다. 그러나 중대형 프로젝트의 경우 문제가 될 수 있습니다. 최신 하드웨어를 사용하여 컴파일하더라도 차이점이 눈에.니다. 그 이유는 컴파일러가 여전히 포함 된 헤더를 열고 구문 분석해야하기 때문입니다. 따라서 빌드를 최적화하려면 위의 기술을 적용하십시오 (최소한 포함 및 forward 선언 사용).

조금 오래되었지만 대규모 C ++ 소프트웨어 디자인 (John Lakos)은이 모든 것을 자세히 설명합니다.


1
이 전략에 동의하지 않습니다 ... 소스 파일에 헤더 파일을 포함하면 모든 종속성을 추적해야합니다. 목록을 작성하고 문서화하는 것보다 직접 포함하는 것이 좋습니다!
앤드류

@Andrew에는 포함 된 내용과 횟수를 확인할 수있는 도구와 스크립트가 있습니다.
BЈовић

1
이 문제를 해결하기 위해 최신 컴파일러 중 일부에서 최적화를 발견했습니다. 그들은 전형적인 보호 진술을 인식하고 처리합니다. 그런 다음 #include를 다시하면 파일로드를 완전히 최적화 할 수 있습니다. 그러나 포워드 선언에 대한 권장 사항은 포함 수를 줄이는 것이 좋습니다. 순방향 선언을 사용하기 시작하면 컴파일러 런타임 (순방향 선언으로 향상됨)과 사용자 친 화성 (편리한 추가 #include로 향상됨)의 균형이되어 각 회사가 다르게 설정하는 균형이됩니다.
Cort Ammon

1
@CortAmmon 일반적인 헤더 가드를 포함 가지고 있지만, 컴파일러는 여전히 그것을 열 수 있고, 그 느린 작업입니다
BЈовић

4
@ BЈовић : 사실은 그렇지 않습니다. 파일에 "일반"헤더 가드가 있음을 인식하고 한 번만 열도록 플래그를 지정하기 만하면됩니다. 예를 들어 Gcc는 언제, 어디서이
Cort Ammon

-4

모범 사례는 헤더 전략이 컴파일되는 한 헤더 전략에 대해 걱정하지 않는 것입니다.

코드의 헤더 섹션은 쉽게 해결할 수있는 컴파일 오류가 발생할 때까지 아무도 보지 않아도되는 블록입니다. 나는 '올바른'스타일에 대한 욕구를 이해하지만 어떤 방식으로도 올바른 것으로 묘사 할 수는 없습니다. 모든 클래스에 헤더를 포함하면 성가신 순서 기반 컴파일 오류가 발생할 가능성이 높지만 이러한 컴파일 오류는 신중한 코딩으로 해결할 수있는 문제를 반영하기도합니다.

그리고 네, 당신은 것입니다 당신이 들어가기 시작하면 그 순서 기반의 문제가있는 friend땅.

두 가지 경우에 문제를 생각할 수 있습니다.


사례 1 : 소수의 클래스가 서로 상호 작용합니다 (예 : 12 개 미만). 서로에 대한 종속성에 영향을 줄 수있는 방식으로 이러한 헤더를 정기적으로 추가, 제거 및 수정합니다. 코드 예제에서 제안하는 경우입니다.

헤더 세트는 작기 때문에 발생하는 모든 문제를 해결하는 데 복잡하지 않을 정도로 작습니다. 하나 또는 두 개의 헤더를 다시 작성하면 어려운 문제가 해결됩니다. 헤더 전략에 대한 걱정은 존재하지 않는 문제를 해결하는 것입니다.


사례 2 : 수십 개의 수업이 있습니다. 일부 클래스는 프로그램의 중추를 나타내며 헤더를 다시 쓰면 많은 양의 코드베이스를 다시 작성 / 재 컴파일해야합니다. 다른 수업에서는이 백본을 사용하여 작업을 수행합니다. 이는 일반적인 비즈니스 설정을 나타냅니다. 헤더는 디렉토리에 분산되어 있으므로 실제로 모든 이름을 기억할 수 없습니다.

솔루션 :이 시점에서 논리 그룹의 클래스를 생각하고 해당 그룹을 헤더로 수집하여 #include계속 반복 하지 못하게해야합니다 . 이렇게하면 인생이 단순해질뿐만 아니라 미리 컴파일 된 헤더 를 활용하는 데 필요한 단계이기도합니다 .

당신 #include은 필요하지 않지만 누가 신경 쓰는 수업 을 끝내는가 ?

이 경우 코드는 다음과 같습니다.

#include <Graphics.hpp>

struct Entity {
    Texture texture;
    RenderObject render();
}

13
솔직히 "좋은 습관은 컴파일하는 한 ____ 전략에 대해 걱정하지 말아야한다"는 형식으로 사람들이 나쁜 판단을 내리게한다고 믿기 때문에 -1해야했습니다. 나는 접근 방식이 가독성을 향한 매우 빠른 것으로 나타 났으며, 가독성은 "작동하지 않는 것"만큼 나쁘다는 것을 알았습니다. 또한 두 경우 모두 결과에 동의하지 않는 많은 주요 라이브러리를 발견했습니다. 예를 들어, Boost DOES는 사례 2에서 권장하는 "컬렉션"헤더를 수행하지만 필요할 때마다 클래스 별 헤더를 제공하는 데 큰 도움이됩니다.
Cort Ammon

3
개인적으로 "컴파일해도 걱정하지 마십시오"라는 것이 "열거 형에 값을 추가 할 때 응용 프로그램을 컴파일하는 데 30 분이 걸립니다. 어떻게 해결할 수 있을까요?"
로봇 고트

내 답변에서 컴파일 시간 문제를 해결했습니다. 사실, 내 대답은 두 가지 중 하나입니다 (둘 중 어느 것도 잘 득점하지 못했습니다). 그러나 실제로 이것은 OP의 질문에 대한 접선입니다. 이것은 "내 변수 이름을 낙타의 경우?" 질문을 입력하십시오. 나는 대답이 인기가 없지만 모든 것에 대해 항상 모범 사례는 아니라는 것을 알고 있으며 이것이 그 중 하나입니다.
QuestionC

# 2에 동의하십시오. 이전 아이디어에 관해서는-로컬 헤더 블록을 업데이트하는 자동화를 원합니다. 그때까지 나는 완전한 목록을 옹호합니다.
chux-복원 Monica Monica

"모든 것을 포함하고 부엌 싱크대"접근 방식은 처음에 시간을 절약 할 수 있습니다. 헤더 파일은 더 작게 보일 수도 있습니다 (대부분의 항목이 간접적으로 포함되어 있기 때문에). 어느 곳에서든 변경 사항이 프로젝트를 30 분 이상 재구성하는 지점에 도달 할 때까지. 그리고 IDE- 스마트 자동 완성 기능은 수백 가지의 관련없는 제안을 제시합니다. 그리고 당신은 우연히 비슷한 이름의 클래스 나 정적 함수 두 개를 섞습니다. 그리고 새로운 구조체를 추가하지만 완전히 관련이없는 클래스와 네임 스페이스 충돌이 발생하여 빌드가 실패합니다.
CharonX
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.