왜 헤더 파일과 .cpp 파일이 있습니까? [닫은]


484

C ++에 헤더 파일과 .cpp 파일이있는 이유는 무엇입니까?



그것은 일반적인 OOP 패러다임이며, h는 클래스 선언이며 cpp는 정의입니다. 구현 방법을 알 필요가 없으며 인터페이스 만 알아야합니다.
Manish Kakati

이것은 인터페이스와 구현을 분리하는 c ++의 가장 좋은 부분입니다. 모든 코드를 단일 파일로 유지하는 것보다 항상 좋습니다. 인터페이스가 분리되어 있습니다. 일부 코드는 항상 헤더 파일의 일부인 인라인 함수와 같습니다. 헤더 파일이 표시되면 선언 된 함수 목록과 클래스 변수가 표시되면 잘 보입니다.
Miank

조직 환경 설정이나 사전 컴파일 된 라이브러리를 배포하는 방법뿐만 아니라 헤더 파일이 컴파일에 필수적인 경우가 있습니다. game.c가 physics.c와 math.c에 의존하는 구조를 가지고 있다고 가정합니다. physics.c는 또한 math.c에 의존합니다. .c 파일을 포함하고 .h 파일을 영원히 잊어 버린 경우 math.c의 중복 선언이 있으며 컴파일의 희망은 없습니다. 이것이 왜 헤더 파일이 중요한지 이해하는 것입니다. 그것이 다른 누군가를 돕기를 바랍니다.
Samy Bencherif

확장자에는 영숫자 문자 만 허용된다는 사실과 관련이 있다고 생각합니다. 나는 그것이 사실인지조차 모른다. 단지 추측
만한다.

답변:


201

글쎄, 주된 이유는 인터페이스를 구현에서 분리하는 것입니다. 헤더는 클래스 (또는 구현되는 모든 것)가 수행 할 "무엇"을 선언하지만 cpp 파일은 "어떻게"기능을 수행 할 것인지를 정의합니다.

이것은 헤더를 사용하는 코드가 구현의 모든 세부 사항과 그에 필요한 다른 클래스 / 헤더를 알아야 할 필요가 없도록 종속성을 줄입니다. 이렇게하면 구현 시간이 변경 될 때 컴파일 시간과 재 컴파일 양이 줄어 듭니다.

완벽하지는 않으며 일반적으로 인터페이스와 구현을 올바르게 분리하기 위해 Pimpl Idiom 과 같은 기술에 의존 하지만 좋은 출발입니다.


177
사실이 아닙니다. 헤더에는 여전히 구현의 주요 부분이 포함되어 있습니다. 개인 인스턴스 변수는 언제 클래스 인터페이스의 일부였습니까? 개인 멤버 기능? 그렇다면 공개적으로 보이는 헤더에서 무엇을하고 있습니까? 그리고 템플릿과 더 떨어져 있습니다.
jalf December

13
그것이 내가 완벽하지 않다고 말한 이유이며, 더 많은 분리를 위해서는 Pimpl 관용구가 필요합니다. 템플릿은 완전히 다른 웜 캔일 수 있습니다. "exports"키워드가 대부분의 컴파일러에서 완벽하게 지원 되더라도 실제 분리보다는 구문 설탕이됩니다.
Joris Timmermans 8

4
다른 언어는 이것을 어떻게 처리합니까? 예를 들어-Java? Java에는 헤더 파일 개념이 없습니다.
Lazer

8
@Lazer : Java는 파싱하기가 더 간단합니다. Java 컴파일러는 다른 파일의 모든 클래스를 몰라도 파일을 구문 분석하고 나중에 유형을 확인할 수 있습니다. C ++에서는 많은 정보가 형식 정보없이 모호하므로 C ++ 컴파일러는 파일을 구문 분석하기 위해 참조 된 형식에 대한 정보가 필요합니다. 그것이 헤더가 필요한 이유입니다.
Niki

15
@nikie : 파싱의 "쉬움"은 무엇과 관련이 있습니까? Java에 최소한 C ++만큼 복잡한 문법이있는 경우 여전히 Java 파일을 사용할 수 있습니다. 두 경우 모두 C는 어떻습니까? C는 파싱하기 쉽지만 헤더와 c 파일을 모두 사용합니다.
Thomas Eding

609

C ++ 컴파일

C ++ 컴파일은 다음 두 가지 주요 단계로 수행됩니다.

  1. 첫 번째는 "원본"텍스트 파일을 이진 "객체"파일로 컴파일하는 것입니다. CPP 파일은 컴파일 된 파일이며 원시 선언 또는 헤더 포함. CPP 파일은 일반적으로 .OBJ 또는 .O "object"파일로 컴파일됩니다.

  2. 두 번째는 모든 "객체"파일을 연결하여 최종 이진 파일 (라이브러리 또는 실행 파일)을 만드는 것입니다.

HPP는이 모든 과정에서 어디에 적합합니까?

열악한 외로운 CPP 파일 ...

각 CPP 파일의 컴파일은 다른 모든 CPP 파일과 독립적이므로 A.CPP가 B.CPP에 정의 된 기호가 필요한 경우 다음과 같습니다.

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

A.CPP에 "doSomethingElse"가 존재하는지 알 수있는 방법이 없기 때문에 컴파일되지 않습니다. A.CPP에 다음과 같은 선언이없는 한 :

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

그런 다음 동일한 기호를 사용하는 C.CPP가 있으면 선언을 복사 / 붙여 넣습니다.

복사 / 페이스트 경고!

예, 문제가 있습니다. 복사 / 붙여 넣기는 위험하며 유지 관리가 어렵습니다. 복사 / 붙여 넣기를하지 않고 심볼을 계속 선언 할 수있는 방법이 있다면 멋질 것입니다. 어떻게하면 되나요? 일반적으로 .h, .hxx, .h ++ 또는 C ++ 파일에 선호되는 .hpp 접미사로 일부 텍스트 파일을 포함하면 다음과 같습니다.

// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;

// A.CPP
#include "B.HPP"

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

// B.CPP
#include "B.HPP"

void doSomethingElse()
{
   // Etc.
}

// C.CPP
#include "B.HPP"

void doSomethingAgain()
{
   doSomethingElse() ; // Defined in B.CPP
}

어떻게 include작동합니까?

파일을 포함하면 본질적으로 CPP 파일의 내용을 구문 분석 한 후 복사하여 붙여 넣습니다.

예를 들어, A.HPP 헤더가있는 다음 코드에서 :

// A.HPP
void someFunction();
void someOtherFunction();

... 원본 B.CPP :

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

... 포함 후 :

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

한 가지 작은 것-왜 B.CPP에 B.HPP를 포함 시키는가?

현재의 경우에는 이것이 필요하지 않으며 B.HPP에는 doSomethingElse함수 선언이 있고 B.CPP에는doSomethingElse 함수 정의 (그 자체로 선언)가 있습니다. 그러나 B.HPP가 선언 (및 인라인 코드)에 사용되는보다 일반적인 경우에는 해당 정의 (예 : 열거 형, 일반 구조체 등)가 없으므로 B.CPP 인 경우 포함이 필요할 수 있습니다 B.HPP의 선언을 사용합니다. 대체로 소스가 기본적으로 헤더를 포함하는 것이 "좋은 맛"입니다.

결론

C ++ 컴파일러는 기호 선언 만 검색 할 수 없으므로 헤더 파일이 필요하므로 이러한 선언을 포함 시켜서이를 지원해야합니다.

마지막으로 한마디 : HPP 파일의 내용 주위에 헤더 가드를 두어야 여러 포함이 아무 것도 깨지지 않도록해야합니다. 그러나 HPP 파일이 존재하는 주된 이유는 위에 설명되어 있습니다.

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

또는 더 간단한

#pragma once

// The declarations in the B.hpp file

2
@nimcap : You still have to copy paste the signature from header file to cpp file, don't you?: 필요 없습니다. CPP가 HPP를 "포함"하는 한, 프리 컴파일러는 HPP 파일의 내용을 CPP 파일에 자동으로 복사하여 붙여 넣습니다. 나는 그것을 명확히하기 위해 대답을 업데이트했습니다.
paercebal

7
@ 밥 : While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself. 아닙니다. 사용자가 제공 한 유형 만 알기 때문에 반 시간 동안 반환 값을 읽지 않아도됩니다. 그런 다음 암시 적 변환이 발생합니다. 그런 다음 코드 가 있으면 함수 foo(bar)인지 확신 할 수 없습니다 foo. 따라서 컴파일러는 헤더의 정보에 액세스하여 소스가 올바르게 컴파일되는지 여부를 결정해야합니다. 그런 다음 코드가 컴파일되면 링커는 함수 호출을 함께 연결합니다.
paercebal

3
@Bob : [continuing] ... 이제 링커는 컴파일러가 수행 한 작업을 수행 할 수 있습니다. 그러면 옵션이 가능해집니다. (이것은 다음 표준에 대한 "모듈"제안의 주제라고 생각합니다). Seems, they're just a pretty ugly arbitrary design.: C ++이 2012 년에 만들어 졌다면, 정말로. 그러나 C ++은 1980 년대에 C를 기반으로 구축되었으며 당시에는 제약 조건이 상당히 달랐습니다 (IIRC, 채택 목적으로 C와 동일한 링커를 유지하기로 결정했습니다).
paercebal

1
@paercebal 설명과 메모, paercebal에 감사드립니다! foo(bar)포인터로 얻은 경우 함수 인지 확실하지 않은 이유는 무엇 입니까? 사실, 나쁜 디자인에 대해서는 C ++이 아니라 C를 비난합니다. 나는 헤더 파일을 가지고 있거나 함수가 하나의 값만 반환하는 것과 같은 순수한 C의 제약을 좋아하지 않는다. ; 왜 여러개의 논증이 있지만, 단 하나의 결과물인가?) :)
Boris Burkov

1
@Bobo : Why can't I be sure, that foo(bar) is a function: foo는 유형일 수 있으므로 클래스 생성자가 있습니다. In fact, speaking of bad design, I blame C, not C++: 나는 많은 것들에 대해 C를 비난 할 수 있지만 70 년대에 설계된 것은 그중 하나가 아닙니다. 다시 말하지만, 그 시간의 제약은 ... such as having header files or having functions return one and only one value: 튜플은 그것을 완화시키고 참조로 인수를 전달하는 데 도움이 될 수 있습니다. 이제 반환 된 여러 값을 검색하는 구문은 무엇이며 언어를 변경하는 것이 가치가 있습니까?
paercebal

93

개념이 시작된 C는 30 년이 지난 지금, 여러 파일의 코드를 함께 연결할 수있는 유일한 방법이었습니다.

오늘날 C ++에서 컴파일 시간을 완전히 없애고 무수한 의존성을 유발하는 끔찍한 해킹입니다 (헤더 파일의 클래스 정의가 구현에 대해 너무 많은 정보를 노출하기 때문에).


3
헤더 파일 (또는 컴파일 / 링크에 실제로 필요한 것)이 왜 단순히 "자동 생성"되지 않았는지 궁금합니다.
Mateen Ulhaq

54

C ++에서 최종 실행 코드는 기호 정보를 전달하지 않기 때문에 다소 순수한 기계 코드입니다.

따라서 코드 자체와는 별도의 코드 인터페이스를 설명 할 방법이 필요합니다. 이 설명은 헤더 파일에 있습니다.


16

C ++은 C에서 상속했기 때문에 불행히도.


4
C에서 C ++의 상속이 왜 불행한가?
Lokesh

3
@Lokesh 수하물 때문에 :(

1
이것이 어떻게 대답이 될 수 있습니까?
Shuvo Sarker

14
@ShuvoSarker 수천 개의 언어가 시연 한 것처럼 프로그래머가 함수 시그니처를 두 번 쓰도록하는 C ++에 대한 기술적 설명은 없습니다. "왜?"에 대한 답변 "역사"입니다.
보리스

15

라이브러리 형식을 디자인 한 사람들은 C 전 처리기 매크로 및 함수 선언과 같이 거의 사용되지 않는 정보를위한 공간을 "폐기"하고 싶지 않기 때문입니다.

컴파일러에게 "이 기능은 나중에 링커가 작업을 수행 할 때 사용할 수 있습니다"라고 알리기 위해 해당 정보가 필요하기 때문에이 공유 정보를 저장할 수있는 두 번째 파일이 필요했습니다.

C / C ++ 이후의 대부분의 언어는이 정보를 출력 (예 : Java 바이트 코드)에 저장하거나 사전 컴파일 된 형식을 전혀 사용하지 않고 항상 소스 형식으로 배포되고 즉시 컴파일됩니다 (Python, Perl).


순환 참조는 작동하지 않습니다. 즉, b.cpp에서 b.lib를 빌드하기 전에 a.cpp에서 a.lib를 빌드 할 수 없지만 a.lib 전에 b.lib를 빌드 할 수는 없습니다.
MSalters

20
자바는 파이썬이 그것을 할 수 있고 모든 현대 언어가 그것을 할 수 있다고 해결했습니다. 그러나 C가 발명되었을 때 RAM이 너무 비싸고 부족했기 때문에 옵션이 아니 었습니다.
Aaron Digulla

6

인터페이스를 선언하는 전 처리기 방식입니다. 인터페이스 (메소드 선언)를 헤더 파일에, 구현을 cpp에 넣습니다. 라이브러리를 사용하는 응용 프로그램은 인터페이스 만 알아야하며 #include를 통해 액세스 할 수 있습니다.


4

종종 전체 코드를 제공하지 않고도 인터페이스 정의를 원할 것입니다. 예를 들어, 공유 라이브러리가있는 경우 공유 라이브러리에 사용 된 모든 기능과 기호를 정의하는 헤더 파일이 제공됩니다. 헤더 파일이 없으면 소스를 배송해야합니다.

단일 프로젝트 내에서 헤더 파일은 IMHO라는 최소한 두 가지 목적으로 사용됩니다.

  • 명확성, 즉 인터페이스를 구현과 분리하여 유지하면 코드를보다 쉽게 ​​읽을 수 있습니다.
  • 컴파일 시간 가능한 경우 전체 구현 대신 인터페이스 만 사용하면 컴파일러에서 실제 코드를 구문 분석하는 대신 인터페이스를 참조 할 수 있기 때문에 컴파일 시간을 줄일 수 있습니다 (이상적으로는 수행해야 함). 한 번).

3
도서관 벤더가 왜 생성 된 "헤더"파일을 배송 할 수 없었습니까? 프리 프로세서 프리 "헤더"파일은 구현이 실제로 중단되지 않는 한 훨씬 더 나은 성능을 제공해야합니다.
Tom Hawtin-tackline

헤더 파일이 생성되거나 손으로 작성된다면 문제는 "사람들이 왜 헤더 파일을 작성합니까?"가 아니라 "헤더 파일이 왜 필요한가"였습니다. 프리 프로세서 프리 헤더에 대해서도 마찬가지입니다. 물론 더 빠를 것입니다.

-5

에 응답 MadKeithV의 대답은 ,

이것은 헤더를 사용하는 코드가 구현의 모든 세부 사항과 그에 필요한 다른 클래스 / 헤더를 알아야 할 필요가 없도록 종속성을 줄입니다. 이렇게하면 컴파일 시간이 줄어들고 구현의 내용이 변경 될 때 필요한 재 컴파일 양도 줄어 듭니다.

또 다른 이유는 헤더가 각 클래스에 고유 한 ID를 제공하기 때문입니다.

우리가 같은 것을 가지고 있다면

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

A는 B의 일부이므로 헤더를 사용하면 이러한 종류의 두통을 피할 수 있기 때문에 프로젝트를 빌드하려고 할 때 오류가 발생합니다.

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