.cpp 파일 만 포함 할 때 모든 것이 작동하는 동안 왜 .h를 포함시켜야합니까?


18

파일을 포함 시켜서 만 작동하게 하려면 왜 파일 .h.cpp파일을 모두 포함해야 .cpp합니까?

예를 들어, file.h포함 선언을 작성한 다음 file.cpp포함 정의 를 작성하고에 둘 다 포함합니다 main.cpp.

또는를 file.cpp포함하는 선언 / 정의 (시제품 없음)를 작성합니다 main.cpp.

둘 다 나를 위해 일합니다. 차이점을 볼 수 없습니다. 컴파일 및 연결 프로세스에 대한 통찰력이 도움이 될 수 있습니다.


귀하의 연구를 공유하면 모든 사람을 도울 수 있습니다. 당신이 무엇을 시도했고 왜 그것이 당신의 요구를 충족시키지 못했는지 알려주십시오. 이것은 당신이 시간을내어 자신을 돕기 위해 노력했고, 분명한 답변을 되풀이하는 것을 막아 주며, 무엇보다도보다 구체적이고 관련있는 답변을 얻는 데 도움이됩니다. 또한 물어
gnat

측면에서 관련 질문을 확인하는 것이 좋습니다. programmers.stackexchange.com/questions/56215/…programmers.stackexchange.com/questions/115368/… 는 좋은 평가입니다

이것이 프로그래머가 아닌 왜 프로그래머가 아닌가?
Monica와의 가벼움 경주

@LightnessRacesInOrbit는 코딩 방법에 대한 질문이며 "방법"질문이 아니기 때문입니다.
CashCow

@CashCow : "방법"사이트 가 아닌 SO를 오해하는 것처럼 들립니다 . 또한 코딩 스타일의 문제 가 아닌 이 질문을 오해하는 것처럼 들립니다 .
Monica와의 가벼움 경주

답변:


29

언급 한대로 파일을 포함 할 있지만 .cpp이것은 나쁜 생각입니다.

언급했듯이 선언은 헤더 파일에 속합니다. 구현이 포함되어 있지 않기 때문에 여러 컴파일 단위에 포함될 때 문제가 발생하지 않습니다. 함수 또는 클래스 멤버의 정의를 여러 번 포함하면 링커가 혼란스러워지고 오류가 발생하기 때문에 항상 문제는 아닙니다 (항상 그런 것은 아님).

.cpp파일에는 클래스, 논리적으로 구성된 함수 그룹, 전역 정적 변수 (예 : 조금만 사용) 등과 같은 프로그램의 하위 세트에 대한 정의가 포함됩니다.

컴파일 단위 ( .cpp파일)에는 포함 된 정의를 컴파일하는 데 필요한 선언이 포함됩니다. 그것은 참조하지만 포함하지 않은 함수와 클래스를 추적하므로 링커는 나중에 객체 코드를 실행 파일이나 라이브러리에 결합 할 때이를 해결할 수 있습니다.

  • Foo.h -> 클래스 Foo에 대한 선언 (인터페이스)을 포함합니다.
  • Foo.cpp -> 클래스 Foo에 대한 정의 (구현)를 포함합니다.
  • Main.cpp-> 주요 방법, 프로그램 진입 점을 포함합니다. 이 코드는 Foo를 인스턴스화하여 사용합니다.

모두 Foo.cppMain.cpp필요 포함합니다 Foo.h. Foo.cpp클래스 인터페이스를 지원하는 코드를 정의하기 때문에 필요하므로 해당 인터페이스가 무엇인지 알아야합니다. Main.cppFoo를 생성하고 동작을 호출하기 때문에 필요하므로 해당 동작이 무엇인지, 메모리의 Foo 크기 및 함수를 찾는 방법 등을 알아야하지만 실제 구현은 아직 필요하지 않습니다.

컴파일러는 생성 Foo.o에서 Foo.cpp컴파일 된 형태로 푸 클래스의 코드를 모두 포함한다. 또한 Main.o주 메소드와 Foo 클래스에 대한 해석되지 않은 참조를 포함하는 생성 합니다.

이제 두 개의 객체 파일 Foo.oMain.o실행 파일 을 결합하는 링커가 제공 됩니다. 그것은 해결되지 않은 Foo 참조를 Main.o보지만 Foo.o필요한 기호 를 포함하는 것을 보 므로 "점을 연결하여" 말해 줍니다. 함수 호출 Main.o은 이제 컴파일 된 코드의 실제 위치에 연결되어 런타임시 프로그램이 올바른 위치로 이동할 수 있습니다.

Foo.cpp파일을 포함시킨 경우 Foo 클래스에 대한 두 가지 정의 Main.cpp가 있습니다 . 링커는 이것을보고 "어느 것을 선택해야할지 모르므로 오류입니다."라고 말합니다. 컴파일 단계는 성공하지만 연결은 실패합니다. (컴파일하지 않고 왜 별도의 파일에 있습니까?)Foo.cpp.cpp

마지막으로, 다른 파일 형식에 대한 아이디어는 C / C ++ 컴파일러와 관련이 없습니다. 원하는 언어에 대한 유효한 코드를 포함하는 "텍스트 파일"을 컴파일합니다. 때로는 파일 확장자에 따라 언어를 말할 수 있습니다. 예를 들어, .c컴파일러 옵션이없는 파일을 컴파일하면 C로 가정하고 a .cc또는 .cpp확장자는 C ++로 가정합니다. 그러나 컴파일러에게 파일 .h또는 .docxC ++로 컴파일하도록 쉽게 지시 할 수 .o있으며 일반 텍스트 형식의 유효한 C ++ 코드가 포함되어 있으면 객체 ( ) 파일을 생성합니다. 이 확장은 프로그래머의 이익을위한 것입니다. 내가 볼 경우 Foo.hFoo.cpp, 나는 즉시 먼저 클래스의 선언을 포함하고 두 번째 정의가 포함되어 있다고 가정합니다.


나는 당신이 여기에서 주장하는 것을 이해하지 못합니다. 방금 포함하는 경우 Foo.cppMain.cpp당신은 포함시킬 필요가 없습니다 .h파일을, 당신은 여전히 가독성을 위해 별도의 파일로 분할 코드에서 승리 한 적은 파일이, 당신의 컴파일 명령은 간단합니다. 헤더 파일의 중요성을 이해하지만 이것이 그렇게 생각하지 않습니다
Benjamin Gruenbaum

2
사소한 프로그램의 경우 하나의 파일에 모두 넣으십시오. 프로젝트 헤더도 포함하지 마십시오 (라이브러리 헤더 만). 소스 파일을 포함시키는 것은 여러 컴파일 단위가 있고 실제로는 개별적으로 컴파일하면 가장 작은 프로그램을 제외하고는 문제를 일으키는 나쁜 습관입니다.

2
If you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.질문에 관한 가장 중요한 문장.
marczellm

@Snowman Right, 여러 컴파일 단위에 집중해야한다고 생각합니다. 더 작은 조각으로 코드를 나누기 위해 헤더 파일이 있거나없는 코드를 붙이는 것은 헤더 파일의 목적에 유용하고 직교합니다. 동적 링크, 조건부 링크 및 더 진보 된 빌드가 필요하면 갑자기 매우 유용합니다.
Benjamin Gruenbaum

C / C ++ 컴파일러 / 링커의 소스를 구성하는 방법에는 여러 가지가 있습니다. 질문자는 프로세스를 확신하지 못했기 때문에 코드 구성 방법 및 프로세스 작동 방법에 대한 모범 사례를 설명했습니다. 코드를 다르게 구성 할 수 있다는 것에 대해 머리카락을 나눌 수는 있지만 사실은 여전히 ​​남아 있습니다. 저는 프로젝트의 99 %가 그것을 수행하는 이유를 설명하는 모범 사례입니다. 그것은 잘 작동하고 당신의 정신을 유지합니다.

9

의 역할에 대한 읽기 C 및 C ++ 전처리 개념적으로 C 또는 C ++ 컴파일러의 최초의 "위상"이다 (역사적으로는 별도의 프로그램이었던 /lib/cpp, 현재 성능 향상을 위해 그것은 적절한 컴파일러 내부에 통합되어 cc1 또는 cc1plus). 특히 GNU cpp전 처리기 의 설명서를 읽으십시오 . 따라서 실제로 컴파일러는 개념적으로 먼저 컴파일 단위 (또는 변환 단위 )를 전처리 한 다음 전처리 된 양식으로 작업합니다.

규칙습관 에 따라 헤더 파일 이 포함 된 경우 항상 헤더 파일을 포함 해야합니다 .file.h

  • 매크로 정의
  • 유형 정의 (예를 들어 typedef, struct, class등 ...)
  • static inline함수의 정의
  • 외부 함수 선언.

이것을 헤더 파일에 넣는 것은 관습 (및 편의)의 문제입니다.

물론 구현 file.cpp에는 위의 모든 것이 필요하므로 #include "file.h" 처음에는 원합니다 .

이것은 컨벤션 (그러나 매우 일반적인 컨벤션 )입니다. 헤더 파일을 피하고 해당 내용을 복사하여 구현 파일 (예 : 번역 단위)에 붙여 넣을 수 있습니다. 그러나 당신은 그것을 원하지 않습니다 (아마도 C 또는 C ++ 코드가 자동으로 생성되는 경우를 제외하고 생성 프로그램이 복사 및 붙여 넣기를 수행하여 전 처리기의 역할을 모방하도록 만들 수 있습니다).

요점은 전처리 기가 텍스트 전용 작업을 수행한다는 것 입니다. 원칙적으로 복사 및 붙여 넣기로 완전히 피하거나 다른 "전 처리기"또는 C 코드 생성기 ( gpp 또는 m4 등 )로 대체 할 수 있습니다.

또 다른 문제는 최신 C (또는 C ++) 표준이 여러 표준 헤더를 정의한다는 것입니다. 대부분의 구현은 정말이 구현 표준 (구현 고유의 것)와 같은 헤더 파일을 ,하지만 난 부합하는 구현 표준 구현하는 것이 (같은 포함 할 수있을 것이라고 생각 #include <stdio.h>C에 대한, 또는 #include <vector>C ++에 대한) 예를 들어, 어떤 마술의 트릭 (데이타베이스의 일부를 사용하여 컴파일러 내부 정보 ).

사용하는 경우 GCC의 컴파일러를 (예를 들어, gcc또는 g++) 당신이 사용할 수있는 -H모든 포함에 대한 정보를 얻을 플래그를하고, -C -E플래그는 전처리 된 양식을 얻을 수 있습니다. 물론 전처리에 영향을주는 다른 많은 컴파일러 플래그 -I /some/dir/가 있습니다 (예 : /some/dir/포함 된 파일 검색 을 위해 추가 하고 -D일부 전 처리기 매크로 등을 미리 정의하는 등).

NB. C ++의 향후 버전 (아마도 C ++ 20 , 아마도 나중에)에는 C ++ 모듈 이있을 수 있습니다 .


1
링크가 썩어도 여전히 좋은 대답입니까?
Dan Pichelman

4
나는 그것을 개선하기 위해 대답을 편집했으며, 오랫동안 관련성이 있다고 생각되는 위키 백과 또는 GNU 문서에 대한 링크를 신중하게 선택합니다.
Basile Starynkevitch

3

C ++의 다중 단위 빌드 모델로 인해 프로그램에 한 번만 나타나는 코드를 정의 할 수있는 방법 (정의)이 필요하며 프로그램의 각 변환 단위에 나타나는 코드를 가질 수있는 방법이 필요합니다 (선언).

이것으로부터 C ++ 헤더 관용구가 탄생합니다. 이유는 컨벤션입니다.

당신은 할 수 있습니다 하나의 번역 단위로 전체 프로그램을 덤프하지만, 코드 재사용, 단위 테스트 및 모듈 간 의존성 취급이 소개합니다 문제. 또한 큰 혼란입니다.


0

에서 선택한 답변 왜 우리가 헤더 파일을 작성해야합니까? 합리적인 설명이지만 자세한 내용을 추가하고 싶었습니다.

C / C ++를 가르치고 논의 할 때 헤더 파일에 대한 합리성이 손실되는 것처럼 보입니다.

헤더 파일은 두 가지 응용 프로그램 개발 문제에 대한 솔루션을 제공합니다.

  1. 인터페이스와 구현의 분리
  2. 대규모 프로그램의 컴파일 / 링크 시간 개선

C / C ++는 작은 프로그램에서 매우 큰 수백만 줄, 수천 개의 파일 프로그램으로 확장 할 수 있습니다. 응용 프로그램 개발은 한 개발자 팀에서 수백 명의 개발자로 확장 할 수 있습니다.

개발자는 여러 모자를 착용 할 수 있습니다. 특히 함수 및 클래스에 대한 인터페이스 사용자이거나 함수 및 클래스의 인터페이스 작성자 일 수 있습니다.

함수를 사용하는 경우 함수 인터페이스, 사용할 매개 변수, 함수가 리턴하는 내용 및 함수의 기능을 알아야합니다. 구현을 보지 않고도 헤더 파일에 쉽게 문서화됩니다. 의 구현을 printf언제 읽었 습니까? 우리는 매일 사용합니다.

인터페이스 개발자 인 경우 모자가 다른 방향으로 바뀝니다. 헤더 파일은 공용 인터페이스의 선언을 제공합니다. 헤더 파일은이 인터페이스를 사용하기 위해 다른 구현에 필요한 것을 정의합니다. 이 새로운 인터페이스의 내부 및 개인 정보는 헤더 파일에 선언되지 않아야합니다. 공개 헤더 파일은 누구나 모듈을 사용해야합니다.

대규모 개발의 경우 컴파일 및 링크에 시간이 오래 걸릴 수 있습니다. 몇 분에서 몇 시간까지 (심지어 며칠!). 소프트웨어를 인터페이스 (헤더)와 구현 (소스)으로 나누면 모든 것을 다시 작성하는 대신 컴파일해야하는 파일 만 컴파일 할 수 있습니다.

또한 헤더 파일을 사용하면 개발자는 헤더 파일뿐만 아니라 라이브러리 (이미 컴파일 된)를 제공 할 수 있습니다. 라이브러리의 다른 사용자는 구현이 제대로 보이지 않을 수도 있지만 헤더 파일과 함께 라이브러리를 계속 사용할 수 있습니다. C / C ++ 표준 라이브러리를 사용하여 매일이 작업을 수행합니다.

소규모 응용 프로그램을 개발하는 경우에도 대규모 소프트웨어 개발 기술을 사용하는 것이 좋습니다. 그러나 우리는 왜 우리가 이러한 습관을 사용하는지 기억해야합니다.


0

모든 코드를 하나의 파일에 넣지 않는 이유를 물을 수도 있습니다.

가장 간단한 대답은 코드 유지 관리입니다.

클래스를 작성하는 것이 합리적인 경우가 있습니다.

  • 헤더 내에 모두 인라인
  • 컴파일 단위 내에서 헤더가 전혀 없습니다.

헤더에서 완전히 인라인하는 것이 합리적 인 시간은 클래스가 실제로 몇 가지 기본 게터와 세터가 있고 아마도 멤버를 초기화하기 위해 값을 취하는 생성자가있는 데이터 구조 인 경우입니다.

(모두 인라인되어야하는 템플릿은 약간 다른 문제입니다).

헤더 내에서 클래스를 모두 만드는 다른 시간은 여러 프로젝트에서 클래스를 사용할 수 있고 특히 라이브러리에서 링크를 피해야 할 때입니다.

컴파일 단위 내에 전체 클래스를 포함하고 헤더를 전혀 노출시키지 않는 시간은 다음과 같습니다.

  • 구현 한 클래스에서만 사용되는 "impl"클래스입니다. 해당 클래스의 구현 세부 사항이며 외부 적으로 사용되지 않습니다.

  • 기본 클래스에 대한 포인터 / 참조 / 스마트 포인터를 리턴하는 일종의 팩토리 메소드로 작성된 추상 기본 클래스의 구현. 팩토리 메소드는 클래스 자체에 노출되지 않습니다. 또한 클래스에 정적 인스턴스를 통해 테이블에 자신을 등록하는 인스턴스가있는 경우 팩토리를 통해 노출 될 필요조차 없습니다.

  • "functor"유형 클래스

즉, 다른 사람이 머리글을 포함하지 않으려는 경우.

무슨 생각을하는지 알고 있습니다. 유지 관리를 위해 cpp 파일 (또는 완전히 인라인 된 헤더)을 포함하여 파일을 쉽게 편집하여 코드를 "찾아서"다시 빌드 할 수 있습니다.

그러나 "유지 보수성"은 코드가 깔끔해 보이지 않습니다. 변화의 영향의 문제입니다. 일반적으로 헤더를 변경하지 않고 구현 (.cpp)파일을 변경하면 부작용이 없어 다른 소스를 다시 작성할 필요가 없다는 것이 일반적입니다.

이것은 노크 효과에 대해 걱정하지 않고 그러한 변경을 "허리"하게하며 실제로 "유지 보수성"이 의미하는 것입니다.


-1

눈사람의 예는 .h 파일이 왜 필요한지 정확히 보여주기 위해 약간의 확장자가 필요합니다.

Foo 클래스에 따라 다른 클래스 바를 플레이에 추가하십시오.

Foo.h-> 클래스 Foo에 대한 선언을 포함합니다

Foo.cpp-> Foo 클래스의 정의 (함수)를 포함합니다

Main.cpp->는 Foo 유형의 변수를 사용합니다.

Bar.cpp->는 Foo 유형의 변수도 사용합니다.

이제 모든 cpp 파일에 Foo.h가 포함되어야합니다. 하나 이상의 다른 cpp 파일에 Foo.cpp를 포함시키는 것은 오류가됩니다. Foo 클래스가 두 번 이상 정의 되었기 때문에 링커가 실패합니다.


1
그것도 아닙니다. 포함 가드 를 사용하면 .h파일 에서 해결되는 것과 똑같은 방식으로 해결할 수 있으며 파일과 관련하여 정확히 동일한 문제 입니다. 이것은 파일 이 전혀 아닙니다 . .h.h
Benjamin Gruenbaum

파일마다 작동하는 경우 (가공 전 처리와 같이) include guard를 어떻게 사용할지 잘 모르겠습니다. 이 경우 Main.cpp와 Bar.cpp는 서로 다른 파일이므로 Foo.cpp는 두 파일에 한 번만 포함됩니다 (보호되거나 차이가 없음). Main.cpp와 Bar.cpp는 모두 컴파일됩니다. 그러나 클래스 Foo의 이중 정의로 인해 링크 오류가 계속 발생합니다. 그러나 내가 언급하지 않은 상속도 있습니다. 외부 모듈 (dll) 등에 대한 선언
SkaarjFace

아니요, 하나의 컴파일 단위이며 .cpp파일 을 포함하기 때문에 링크가 전혀 발생하지 않습니다 .
Benjamin Gruenbaum

나는 그것이 컴파일되지 않을 것이라고 말하지 않았다. 그러나 동일한 실행 파일에서 main.o와 bar.o를 모두 링크하지는 않습니다. 링크하지 않으면 컴파일의 요점은 무엇입니까?
SkaarjFace

어 .. 코드를 실행하고 있습니까? 여전히 파일을 연결할 수는 있지만 파일을 서로 연결할 수는 없습니다. 오히려 동일한 컴파일 단위입니다.
Benjamin Gruenbaum

-2

전체 코드를 동일한 파일로 작성하면 코드가보기 흉하게됩니다.
둘째, 당신은 당신의 작문 수업을 다른 사람들과 공유 할 수 없습니다. 소프트웨어 엔지니어링에 따르면 클라이언트 코드를 별도로 작성해야합니다. 클라이언트는 프로그램이 어떻게 작동하는지 알 수 없습니다. 그들은 단지 출력이 필요합니다 전체 파일을 같은 파일에 쓰면 프로그램의 보안이 누출됩니다.

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