C ++에 별도의 헤더 파일이 필요한 이유는 무엇입니까?


138

왜 C ++이 .cpp 파일과 동일한 기능을 가진 별도의 헤더 파일을 필요로하는지 이해하지 못했습니다. 클래스를 작성하고 리팩토링하는 것이 매우 어렵고 불필요한 파일을 프로젝트에 추가합니다. 그리고 헤더 파일을 포함해야하지만 이미 포함되어 있는지 명시 적으로 확인해야하는 문제가 있습니다.

C ++은 1998 년에 비준되었으므로 왜 이렇게 설계 되었습니까? 별도의 헤더 파일을 사용하면 어떤 이점이 있습니까?


후속 질문 :

내가 포함하는 모든 것이 .h 파일 일 때 컴파일러는 코드가 포함 된 .cpp 파일을 어떻게 찾습니까? .cpp 파일의 이름이 .h 파일과 같다고 가정합니까, 아니면 실제로 디렉토리 트리의 모든 파일을 살펴 보나요?


2
단일 파일을 편집하려면 lzz (www.lazycplusplus.com) 만 체크 아웃하십시오.
Richard Corden

답변:


105

헤더 파일에 다른 용도가 있지만 정의와 선언을 분리하는 것에 대해 묻는 것 같습니다.

대답은 C ++이 이것을 "필요하지"않다는 것입니다. 클래스 정의에 정의 된 멤버 함수에 대해 자동으로 모든 것을 인라인으로 표시하면 분리 할 필요가 없습니다. 헤더 파일의 모든 것을 정의 할 수 있습니다.

분리 하려는 이유 는 다음과 같습니다.

  1. 빌드 시간을 향상시킵니다.
  2. 정의의 소스없이 코드에 연결합니다.
  3. 모든 것을 "인라인"으로 표시하지 않기 위해.

더 일반적인 질문이 "왜 C ++과 Java가 동일하지 않습니까?"라면 "왜 Java 대신 C ++를 작성합니까?"라고 물어야합니다. ;-피

더 심각한 이유는 C ++ 컴파일러가 다른 번역 단위에 접근 할 수없고 javac가 할 수 있고하는 방식으로 기호를 사용하는 방법을 알아낼 수 없기 때문입니다. 헤더 파일은 링크 타임에 사용 가능한 것으로 컴파일러에 선언하는 데 필요합니다.

그래서 #include직선 텍스트 대체입니다. 헤더 파일에 모든 것을 정의하면 전처리 기는 프로젝트의 모든 소스 파일을 대량으로 복사하여 붙여 넣고 컴파일러에 공급합니다. C ++ 표준이 1998 년에 비준되었다는 사실은 이와 관련이 없으며, C ++의 컴파일 환경은 C의 컴파일 환경과 매우 밀접한 관련이 있다는 사실입니다.

후속 질문에 답변하기 위해 내 의견을 변환 :

컴파일러는 코드가 포함 된 .cpp 파일을 어떻게 찾습니까?

적어도 헤더 파일을 사용한 코드를 컴파일 할 때가 아닙니다. 링크하는 함수는 아직 작성되어있을 필요는 없으며 컴파일러가 어떤 .cpp파일에 있는지 알지 않아도 됩니다. 컴파일 타임에 호출 코드가 알아야 할 모든 것은 함수 선언에 표시됩니다. 링크 타임에 .o파일 목록 또는 정적 또는 동적 라이브러리 를 제공 할 것이며 사실상 헤더는 함수 정의가 어딘가에있을 것이라는 약속입니다.


3
"분리해야 할 이유는 다음과 같습니다."에 덧붙이려면 & 헤더 파일의 가장 중요한 기능은 다음과 같습니다. 코드 구조 설계와 구현을 분리하는 이유 : A. 많은 오브젝트를 포함하는 정말 복잡한 구조에 들어가면 헤더 파일을 탐색하고 헤더 주석으로 보완되는 방식을 기억하는 것이 훨씬 쉽습니다. B. 한 사람이 모든 객체 구조를 정의하지 않고 다른 사람은 구현을 관리하여 물건을 정리했습니다. 무엇보다도 복잡한 코드를 더 읽기 쉽게 만든다고 생각합니다.
Andres Canella

가장 간단한 방법으로 헤더 대 cpp 파일 분리의 유용성을 생각할 수있는 것은 중간 대 프로젝트에 실제로 도움이되는 인터페이스 대 구현을 분리하는 것입니다.
Krishna Oza

나는 (2), "정의없이 코드에 대한 링크"에 포함되도록 의도했다. OK, 그래서 정의가있는 git repo에 여전히 액세스 할 수 있지만 요점은 종속성 구현과 별도로 코드를 컴파일 한 다음 링크 할 수 있다는 것입니다. 말 그대로 원하는 것이 인터페이스 / 구현을 다른 파일로 분리하고 별도로 빌드 할 필요가 없다면 foo_interface.h에 foo_implementation.h가 포함되도록하면됩니다.
Steve Jessop

4
@AndresCanella 아닙니다. 자신의 코드가 아닌 코드를 읽고 유지하는 것은 악몽입니다. 코드에서 어떤 작업을 수행하는지 완전히 이해하려면 n 파일 대신 2n 파일을 건너 뛰어야합니다. 이것은 Big-Oh 표기법이 아니며 2n은 n과 비교하여 많은 차이를 만듭니다.
Błażej Michalik

1
나는 헤더가 도움이되는 거짓말이라고 두 번째입니다. 예를 들어, minix 소스를 확인하십시오. 제어가 전달되는 곳에서 시작하고, 선언되거나 정의 된 곳을 따라 가기가 너무 어렵습니다. 분리 된 동적 모듈을 통해 빌드 된 경우 한 가지 의미를 이해하여 소화 할 수 있습니다. 의존성 모듈. 대신 헤더를 따라야하며 이런 식으로 작성된 코드를 지옥으로 읽습니다. 반대로 nodejs는 ifdef가없는 곳에서 무엇이 있는지 명확하게하며, 어디에서 왔는지 쉽게 식별 할 수 있습니다.
Dmitry

91

C ++은 C가 그렇게했기 때문에 그렇게하는데, 실제 질문은 C가 왜 그렇게했을까요? Wikipedia 는 이것에 대해 조금 말합니다.

최신 컴파일 언어 (예 : Java, C #)는 앞으로 선언을 사용하지 않습니다. 식별자는 소스 파일에서 자동으로 인식되고 동적 라이브러리 심볼에서 직접 읽습니다. 이것은 헤더 파일이 필요하지 않음을 의미합니다.


13
+1 머리에 못을 박는 다. 이것은 실제로 자세한 설명이 필요하지 않습니다.
MSalters

6
C ++이 왜 전방 선언을 사용해야하는지, 왜 소스 파일에서 식별자를 인식하지 못하고 동적 라이브러리 심볼에서 직접 읽을 수 없는지, 왜 C ++에서 그렇게했는지 찾아봐야합니다. C가 그렇게했기 때문에 : p
Alexander Taylor

3
@AlexanderTaylor :)
Donald Byrd

66

어떤 사람들은 헤더 파일이 장점이라고 생각합니다 :

  • 인터페이스와 구현의 분리를 활성화 / 강화 / 허용한다고 주장하지만 일반적으로 그렇지 않습니다. 헤더 파일은 구현 세부 사항으로 가득합니다 (예 : 클래스의 멤버 변수는 공용 인터페이스의 일부가 아니더라도 헤더에 지정해야 함). 함수는 클래스 선언 에서 인라인 으로 정의 될 수 있으며 종종 정의됩니다 헤더에서 다시이 분리를 파괴합니다.
  • 각 변환 단위를 독립적으로 처리 할 수 ​​있기 때문에 컴파일 타임을 향상시키는 경우가 있습니다. 그러나 C ++은 컴파일 타임과 관련하여 가장 느린 언어 일 것입니다. 이유의 일부는 동일한 헤더를 여러 번 반복해서 포함하기 때문입니다. 여러 번역 단위에 많은 수의 헤더가 포함되어 있으므로 여러 번 구문 분석해야합니다.

궁극적으로 헤더 시스템은 C가 디자인되었을 때 70 년대의 인공물입니다. 당시 컴퓨터에는 메모리가 거의 없었으며 전체 모듈을 메모리에 보관하는 것은 선택 사항이 아닙니다. 컴파일러는 맨 위에서 파일 읽기를 시작한 다음 소스 코드를 통해 선형으로 진행해야했습니다. 헤더 메커니즘이이를 가능하게합니다. 컴파일러는 다른 변환 단위를 고려할 필요가 없으며 코드를 위에서 아래로 읽어야합니다.

그리고 C ++은 이전 버전과의 호환성을 위해이 시스템을 유지했습니다.

오늘날은 말이되지 않습니다. 비효율적이며 오류가 발생하기 쉽고 복잡합니다. 그것이 목표 라면 인터페이스와 구현을 분리하는 훨씬 더 좋은 방법이 있습니다 .

그러나 C ++ 0x에 대한 제안 중 하나는 적절한 모듈 시스템을 추가하여 .NET 또는 Java와 유사한 코드를 더 큰 모듈로 컴파일 할 수 있도록하는 것입니다. 이 제안은 C ++ 0x에서 삭감되지는 않았지만 여전히 "나중에이 작업을 수행하고 싶습니다"범주에 속한다고 생각합니다. 아마도 TR2 또는 이와 유사한 것입니다.


이 페이지에서 가장 좋은 답변입니다. 감사합니다!
척 레 버트

29

내 (제한적-나는 일반적으로 C 개발자가 아닙니다) 이해에, 이것은 C에 뿌리를두고 있습니다. C는 클래스 또는 네임 스페이스가 무엇인지 알지 못한다는 것을 하나의 긴 프로그램 일뿐입니다. 또한 함수를 사용하기 전에 선언해야합니다.

예를 들어, 다음은 컴파일러 오류를 제공해야합니다.

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

선언하기 전에 호출하기 때문에 "SomeOtherFunction이 선언되지 않았습니다"라는 오류가 발생합니다. 이 문제를 해결하는 한 가지 방법은 SomeOtherFunction을 SomeFunction 위로 이동하는 것입니다. 또 다른 방법은 함수 서명을 먼저 선언하는 것입니다.

void SomeOtherFunction();

void SomeFunction() {
    SomeOtherFunction();
}

void SomeOtherFunction() {
    printf("What?");
}

이를 통해 컴파일러는 다음과 같은 사실을 알 수 있습니다. 코드 어딘가에 보이면 SomeOtherFunction이라는 함수가 있습니다. 따라서 SomeOtherFunction을 호출하는 코드를 권장하는 경우 당황하지 말고 대신 찾으십시오.

이제 두 개의 다른 .c 파일에 SomeFunction과 SomeOtherFunction이 있다고 가정하십시오. 그런 다음 Some.c에서 # SomeOther.c를 #include해야합니다. 이제 SomeOther.c에 "비공개"기능을 추가하십시오. C는 개인 함수를 알지 못하므로 Some.c에서도 해당 함수를 사용할 수 있습니다.

이것은 .h 파일이 들어오는 곳입니다. 다른 .c 파일에서 액세스 할 수있는 .c 파일에서 '내보내기'하려는 모든 함수 (및 변수)를 지정합니다. 이렇게하면 공개 / 개인 범위와 같은 것을 얻게됩니다. 또한 소스 코드를 공유 할 필요없이이 .h 파일을 다른 사람에게 제공 할 수 있습니다. .h 파일은 컴파일 된 .lib 파일에 대해서도 작동합니다.

따라서 주된 이유는 실제로 편의성, 소스 코드 보호 및 응용 프로그램 부분간에 약간의 분리가 있기 때문입니다.

그래도 C였습니다. C ++에는 클래스와 개인 / 공개 수정자가 도입되었으므로 여전히 필요한지 물어볼 수는 있지만 C ++ AFAIK는 여전히 사용하기 전에 함수 선언이 필요합니다. 또한 많은 C ++ 개발자는 C 개발자이거나 C 개발자이며 개념과 습관을 C ++로 인수했습니다. 왜 부서지지 않은 것을 변경합니까?


5
컴파일러가 코드를 실행하고 모든 함수 정의를 찾을 수없는 이유는 무엇입니까? 컴파일러에 프로그래밍하기가 매우 쉬운 것 같습니다.
Marius

3
당신 종종없는 소스 를 가지고 있다면 . 컴파일 된 C ++는 코드를로드하고 링크하기에 충분한 추가 정보만으로 효과적으로 기계 코드입니다. 그런 다음 진입 점에서 CPU를 가리키고 실행합니다. 이는 Java 또는 C #과 근본적으로 다릅니다. 여기서 코드는 내용에 메타 데이터가 포함 된 중간 바이트 코드로 컴파일됩니다.
DevSolar

나는 1972 년에 컴파일러에게 다소 비싼 작업 일 수 있다고 생각합니다.
Michael Stum

Michael Stum의 말이 정확합니다. 이 동작이 정의 될 때 단일 변환 단위를 통한 선형 스캔이 실제로 구현 될 수있는 유일한 것입니다.
jalf

3
그렇습니다-테이프 대량 공격으로 16 비터에서 컴파일하는 것은 쉽지 않습니다.
MSalters

11

첫 번째 장점 : 헤더 파일이 없으면 다른 소스 파일에 소스 파일을 포함시켜야합니다. 이로 인해 포함 된 파일이 변경 될 때 포함 파일이 다시 컴파일됩니다.

두 번째 장점 : 서로 다른 유닛 (다른 개발자, 팀, 회사 등)간에 코드를 공유하지 않고도 인터페이스를 공유 할 수 있습니다.


1
예를 들어 C #에서 '다른 소스 파일에 소스 파일을 포함해야합니다'를 암시합니까? 분명히 당신은하지 않기 때문에. 두 번째 장점 들어, 나는 너무 언어에 의존 생각 : 당신은 예를 들어, 델파이 .H 파일을 사용 실 거예요
Vlagged

어쨌든 전체 프로젝트를 다시 컴파일해야하므로 첫 번째 이점이 실제로 중요합니까?
Marius

좋아, 그러나 나는 언어 기능을 생각하지 않습니다. 정의 "문제"전에 C 선언을 다루는 것이 더 실용적입니다. "특징 인 버그는 아닙니다"라고 유명한 사람과 같습니다. :)
neuro

@ 마리우스 : 네, 정말 중요합니다. 전체 프로젝트를 연결하는 것은 전체 프로젝트를 컴파일 및 연결하는 것과 다릅니다. 프로젝트의 파일 수가 증가함에 따라 모든 파일을 컴파일하면 실제로 성가 시게됩니다. @Vlagged : 당신 말이 맞지만, 나는 C ++을 다른 언어와 비교하지 않았습니다. 소스 파일 만 사용하는 것과 소스 및 헤더 파일을 사용하는 것을 비교했습니다.
erelender

C #에는 다른 소스 파일이 포함되어 있지 않지만 여전히 모듈을 참조해야합니다. 그러면 컴파일러에서 소스 파일을 가져 오거나 바이너리에 반영하여 코드에서 사용하는 심볼을 구문 분석합니다.
gbjbaanb

5

헤더 파일의 필요성은 컴파일러가 다른 모듈의 함수 및 / 또는 변수에 대한 유형 정보를 알고 있어야하는 한계로 인해 발생합니다. 컴파일 된 프로그램 또는 라이브러리에는 다른 컴파일 단위로 정의 된 객체에 바인딩하기 위해 컴파일러에서 필요한 유형 정보가 포함되어 있지 않습니다.

이 제한을 보완하기 위해 C 및 C ++에서 선언을 허용하며 이러한 선언을 전 처리기의 #include 지시문을 사용하여이를 사용하는 모듈에 포함시킬 수 있습니다.

반면에 Java 또는 C #과 같은 언어에는 컴파일러 출력 (클래스 파일 또는 어셈블리)에 바인딩하는 데 필요한 정보가 포함됩니다. 따라서 더 이상 모듈의 클라이언트가 독립형 선언을 포함 할 필요가 없습니다.

바인딩 정보가 컴파일러 출력에 포함되지 않는 이유는 간단합니다. 런타임에 필요하지 않습니다 (컴파일시 유형 검사가 수행됨). 공간을 낭비 할뿐입니다. C / C ++는 실행 파일이나 라이브러리의 크기가 상당히 중요한 시점에서 나온다는 것을 기억하십시오.


동의합니다. 여기 비슷한 생각을 가지고 : stackoverflow.com/questions/3702132/...
smwikipedia

4

C ++는 언어 자체에 관한 것이 아닌 C에 대한 어떠한 것도 불필요하게 변경하지 않고 C 프로그래밍에 현대적인 프로그래밍 언어 기능을 추가하도록 설계되었습니다.

그렇습니다.이 시점에서 (첫 번째 C ++ 표준 10 년 후, 사용량이 심각하게 증가한 20 년 후) 적절한 모듈 시스템이없는 이유를 쉽게 알 수 있습니다. 분명히 오늘날 새로운 언어는 C ++처럼 작동하지 않을 것입니다. 그러나 이것이 C ++의 요점이 아닙니다.

C ++의 요점은 진화하고 기존 관행을 원활하게 유지하며 사용자 커뮤니티에 적절하게 작동하는 것들을 너무 자주 파괴하지 않고 새로운 기능을 추가하는 것입니다.

이것은 다른 언어보다 더 어렵게 (특히 새로운 프로젝트를 시작하는 사람들에게) 더 쉬운 것을 만들고 (특히 기존 코드를 유지하는 사람들에게) 더 쉽다는 것을 의미합니다.

따라서 C ++이 C # (이미 C #이있는 것처럼 의미가 없음)으로 바뀔 것으로 기대하는 대신, 작업에 적합한 도구를 선택하는 것이 어떻습니까? 나 자신은 현대 언어로 새로운 기능을 많이 작성하려고 노력하고 있으며 (C #을 사용하게 됨) C ++로 유지하는 기존 C ++이 많이 있습니다. 다시 쓸 가치가 없기 때문입니다. 모두. 그들은 어쨌든 아주 잘 통합되어 있기 때문에 크게 고통스럽지 않습니다.


C #과 C ++를 어떻게 통합합니까? COM을 통해?
Peter Mortensen

1
세 가지 주요 방법이 있습니다. "최상의"는 기존 코드에 따라 다릅니다. 세 가지를 모두 사용했습니다. 내가 가장 많이 사용하는 것은 COM입니다. 기존 코드가 이미 그 주위에 설계 되었기 때문에 실제로는 매끄럽고 잘 작동합니다. 어떤 이상한 곳에서는 COM 인터페이스가없는 상황에 대해 매우 매끄러운 통합을 제공하는 C ++ / CLI를 사용합니다 (존재하는 COM 인터페이스를 사용하더라도 기존 COM 인터페이스를 사용하는 것을 선호 할 수 있음). 마지막으로 p / invoke가 있으며 기본적으로 DLL에서 노출 된 C와 같은 함수를 호출 할 수 있으므로 C #에서 Win32 API를 직접 호출 할 수 있습니다.
Daniel Earwicker

4

음, C ++은 1998 년에 비준되었지만 그보다 훨씬 더 오랫동안 사용되어 왔으며 비준은 주로 구조를 강요하기보다는 현재 사용량을 줄였습니다. C ++은 C를 기반으로하고 C에는 헤더 파일이 있으므로 C ++에도 헤더 파일이 있습니다.

헤더 파일의 주요 이유는 파일을 별도로 컴파일하고 종속성을 최소화하기위한 것입니다.

foo.cpp가 있고 bar.h / bar.cpp 파일의 코드를 사용하고 싶다고 가정 해보십시오.

foo.cpp에 "bar.h"를 #include 한 다음 bar.cpp가없는 경우에도 foo.cpp를 프로그래밍하고 컴파일 할 수 있습니다. 헤더 파일은 bar.h의 클래스 / 함수가 런타임에 존재할 것이라는 컴파일러의 약속 역할을하며 이미 알아야 할 모든 것을 갖추고 있습니다.

물론 프로그램을 연결하려고 할 때 bar.h의 함수에 본문이 없으면 연결되지 않으며 오류가 발생합니다.

부작용은 소스 코드를 공개하지 않고도 사용자에게 헤더 파일을 제공 할 수 있다는 것입니다.

또 다른 방법은 * .cpp 파일에서 코드 구현을 변경했지만 헤더를 전혀 변경하지 않으면 * .cpp 파일을 사용하는 모든 파일 대신 컴파일해야합니다. 물론 많은 구현을 헤더 파일에 넣으면 유용성이 떨어집니다.


3

main과 동일한 기능을 가진 별도의 헤더 파일이 필요하지 않습니다. 여러 코드 파일을 사용하여 응용 프로그램을 개발하고 이전에 선언되지 않은 함수를 사용하는 경우에만 필요합니다.

실제로 범위 문제입니다.


1

C ++은 1998 년에 비준되었으므로 왜 이렇게 설계 되었습니까? 별도의 헤더 파일을 사용하면 어떤 이점이 있습니까?

실제로 헤더 파일은 프로그램을 처음으로 검사 할 때 매우 유용합니다. 헤더 파일 (텍스트 편집기 만 사용)을 체크 아웃하면 고급 도구를 사용하여 클래스를보고 다른 언어와 달리 프로그램 아키텍처에 대한 개요를 제공합니다. 그들의 회원 기능.


1

헤더 파일의 실제 (역사적) 이유는 컴파일러 개발자에게 더 쉬워 졌다고 생각하지만 헤더 파일 이점을 제공합니다. 자세한 내용은 이전 게시물
확인하십시오 ...


1

헤더 파일없이 C ++을 완벽하게 개발할 수 있습니다. 실제로 템플릿을 집중적으로 사용하는 일부 라이브러리는 헤더 / 코드 파일 패러다임을 사용하지 않습니다 (부스트 참조). 그러나 C / C ++에서는 선언되지 않은 것을 사용할 수 없습니다. 이를 처리하는 한 가지 실용적인 방법은 헤더 파일을 사용하는 것입니다. 또한 코드 / 구현을 공유하지 않고도 인터페이스를 공유 할 수있는 이점이 있습니다. 그리고 나는 그것이 C 제작자에 의해 구상되지 않았다고 생각합니다 : 공유 헤더 파일을 사용할 때 유명한 것을 사용해야합니다 :

#ifndef MY_HEADER_SWEET_GUARDIAN
#define MY_HEADER_SWEET_GUARDIAN

// [...]
// my header
// [...]

#endif // MY_HEADER_SWEET_GUARDIAN

그것은 실제로 언어 기능이 아니라 다중 포함을 다루는 실용적인 방법입니다.

따라서 C가 만들어 졌을 때 앞으로 선언하는 문제는 과소 평가되었으며 이제는 C ++과 같은 고급 언어를 사용할 때 이러한 종류의 문제를 처리해야한다고 생각합니다.

우리에게 가난한 C ++ 사용자를위한 또 다른 부담은 ...


1

컴파일러가 다른 파일에 정의 된 심볼을 자동으로 찾게하려면 프로그래머가 해당 파일을 미리 정의 된 위치에 두도록해야합니다 (Java 패키지 구조가 프로젝트의 폴더 구조를 결정하는 것과 같이). 헤더 파일을 선호합니다. 또한 사용하는 라이브러리 소스 또는 컴파일러에 필요한 정보를 이진 파일에 넣는 일정한 방법이 필요합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.