헤더 파일에 C ++ 정의를 배치하는 것이 좋은 방법입니까?


196

C ++을 사용하는 개인 스타일은 항상 클래스 선언을 포함 파일에, .cpp 파일에 정의를 넣어야합니다. Loki의 C ++ 헤더 파일, 코드 분리에 대한 답변 과 매우 유사 합니다. 분명히, 내가이 스타일을 좋아하는 이유 중 일부는 아마도 Modula-2와 Ada를 코딩하는 데 소비했던 모든 해와 관련이있을 것입니다.이 두 가지 모두 사양 파일과 본문 파일과 유사한 체계를 가지고 있습니다.

가능한 모든 C ++ 선언이 가능한 경우 헤더 파일에 정의를 포함시켜야한다고 주장하는 C ++에 대한 지식이 많은 동료가 있습니다. 그는 이것이 유효한 대체 스타일이거나 심지어 약간 더 나은 스타일이라고 말하지는 않지만 이제는 모두가 C ++에 사용하는 보편적으로 받아 들여지는 새로운 스타일입니다.

나는 내가 예전만큼 유연하지 않았기 때문에, 나는 그와 함께 몇 명의 사람들을 더 볼 때까지이 밴드의 마차에 움켜 쥐는 것이 정말로 불안하지 않다. 그렇다면이 관용구는 얼마나 흔한가?

답변에 구조를 부여하기 위해 : 지금 은 길 입니까 , 매우 흔하고, 다소 흔하거나, 드물거나, 버그가 있습니까?


헤더의 한 줄 함수 (getter 및 setter)가 일반적입니다. 두 번째로 눈에 띄는 것보다 길다. 아마도 같은 헤더의 다른 클래스에서만 사용되는 작은 클래스의 완전한 정의입니까?
Martin Beckett

나는 항상 모든 클래스 정의를 헤더에 넣었습니다. pimpl 클래스에 대한 정의 만 예외입니다. 헤더로만 선언합니다.
Johannes Schaub-litb

3
어쩌면 그는 Visual C ++에서 코드 작성을 주장하는 방식 때문에 그렇게 생각할 수도 있습니다. 버튼을 클릭하면 구현이 헤더 파일에 생성됩니다. 다른 사람들이 아래에서 설명 한 이유로 Microsoft가 왜 이것을 권장하는지 모르겠습니다.
WKS

4
@WKS-Microsoft는 모두 C #으로 프로그래밍하고 C #에서는 "헤더"와 "본문"구분이 없으며 하나의 파일 일뿐입니다. C ++과 C # 세계에서 오랫동안 사용되어 왔지만 C # 방식은 실제로 다루기가 훨씬 쉽습니다.
Mark Lakata

1
@MarkLakata-그것은 그가 지적한 것 중 하나입니다. 요즘 그를이 인수를 들었하지 않은,하지만 IIRC 그는 자바와 C # 작업이 이런 식으로 주장되었고, C #은 모든 언어가 곧 다음됩니다 그것을 추세를했다하는 시간에 새로운이었다
TED

답변:


209

동료가 잘못되었습니다. 일반적인 방법은 항상 .cpp 파일 (또는 원하는 확장자)에 코드를 넣고 헤더에 선언하는 것입니다.

헤더에 코드를 넣는 것이 때로는 장점이 있습니다. 이렇게하면 컴파일러가 더 영리하게 인라인 할 수 있습니다. 그러나 동시에 모든 코드는 컴파일러에 포함될 때마다 처리되어야하므로 컴파일 시간을 파괴 할 수 있습니다.

마지막으로, 모든 코드가 헤더 일 때 순환 객체 관계 (때때로 원하는 경우)를 갖는 것은 성가신 일입니다.

결론은, 당신이 맞았어요. 그는 틀 렸습니다.

편집 : 나는 당신의 질문에 대해 생각하고 있습니다. 그가 말한 것이 사실 인 경우 가 하나 있습니다 . 템플릿. boost와 같은 많은 최신 "현대"라이브러리는 템플릿을 많이 사용하며 종종 "헤더 만"입니다. 그러나 템플릿을 다룰 때 수행 할 수있는 유일한 방법이므로 템플릿을 다룰 때만 수행해야합니다.

편집 : 일부 사람들은 좀 더 설명을 원합니다. 여기에 "헤더 전용"코드 작성에 대한 단점이 있습니다.

주변을 검색하면 부스트를 처리 할 때 컴파일 시간을 줄이는 방법을 찾는 많은 사람들이 있습니다. 예 : Boost Asio를 사용하여 컴파일 시간을 줄이는 방법- 부스트가 포함 된 단일 1K 파일의 14 초 컴파일을 볼 수 있습니다. 14s는 "폭발"하지 않는 것 같지만 확실히 일반적인 것보다 훨씬 길며 매우 빠르게 누적 될 수 있습니다. 큰 프로젝트를 다룰 때. 헤더 전용 라이브러리는 상당히 측정 가능한 방식으로 컴파일 시간에 영향을줍니다. 부스트가 너무 유용하기 때문에 우리는 그것을 용납합니다.

또한 헤더에서만 수행 할 수없는 많은 것들이 있습니다 (부스트조차도 스레드, 파일 시스템 등과 같은 특정 부분에 링크 해야하는 라이브러리가 있습니다). 기본 예는 여러 정의 오류가 발생할 때 헤더 전용 라이브러리에 단일 전역 객체를 가질 수 없다는 것입니다 (단일 인 가증에 의존하지 않는 한). 참고 : C ++ 17의 인라인 변수는 나중에이 특정 예제를 실행할 수있게합니다.

마지막으로, 헤더 전용 코드의 예로 부스트를 사용하면 큰 세부 사항이 종종 누락됩니다.

Boost는 사용자 수준 코드가 아닌 라이브러리입니다. 자주 바뀌지 않습니다. 사용자 코드에서 모든 것을 헤더에 넣으면 작은 변화가있을 때마다 전체 프로젝트를 다시 컴파일해야합니다. 그것은 엄청난 시간 낭비입니다 (그리고 컴파일에서 컴파일로 변경되지 않는 라이브러리의 경우는 아닙니다). 헤더 / 소스와 더 나은 부분을 나눌 때 포워드 선언을 사용하여 포함을 줄이면 하루에 추가 할 때 재 컴파일 시간을 절약 할 수 있습니다.


16
나는 그것이 그가 어디에서 왔는지 확신합니다. 이것이 나타날 때마다 템플릿을 불러옵니다. 그의 주장은 대략 모든 코드를 일관성있게 수행해야한다는 것입니다.
TED

14
그 : 귀하의 총에 가난한 그가 제작의 주장, 스틱입니다
에반 테란에게

11
"export"키워드가 지원되는 경우 템플리트 정의는 CPP 파일에있을 수 있습니다. 그것은 내가 아는 한, 대부분의 컴파일로도 구현되지 않는 C ++의 어두운 구석입니다.
Andrei Taranchenko 님이

2
: 예는이 답변의 하단 (상단 다소 뒤얽힌한다)를 참조하십시오 stackoverflow.com/questions/555330/...
에반 테란

3
"Hooray, no linker errors"에서이 토론에 의미가있게됩니다.
Evan Teran

158

C ++ 코더들이 The Way에 동의 한 날 , 양들은 사자와 함께 누워 팔레스타인 사람들은 이스라엘 사람들을 받아들이고 고양이와 개들은 결혼 할 수있게됩니다.

.h와 .cpp 파일 사이의 분리는이 시점에서 대부분 임의적이며, 오래 전의 컴파일러 최적화의 흔적입니다. 내 눈에 선언은 헤더에 속하고 정의는 구현 파일에 속합니다. 그러나 그것은 단지 습관이 아니라 종교입니다.


140
"C ++ 코더가 The Way에 동의 한 날 ..."하나의 C ++ 코더 만 남게됩니다!
브라이언 엔싱크

9
나는 그들이 이미 길에 통화 당에서 .H의 선언과 정의에 동의 생각
하센

6
우리는 모두 맹인이며 C ++는 코끼리입니다.
Roderick Taylor

습관? .h를 사용하여 범위를 정의하는 것은 어떻습니까? 어느 것이 대체 되었습니까?
Hernán Eche

28

헤더 코드는 선언이 아닌 실제 코드를 변경할 때 헤더를 포함하는 모든 파일을 강제로 다시 컴파일해야하므로 일반적으로 나쁜 생각입니다. 또한 헤더를 포함하는 모든 파일에서 코드를 구문 분석해야하기 때문에 컴파일 속도가 느려집니다.

헤더 파일에 코드가있는 이유는 일반적으로 키워드 인라인이 제대로 작동하고 다른 cpp 파일에서 인스턴스화되는 템플릿을 사용할 때 필요하기 때문입니다.


1
"선언보다는 실제 코드를 변경할 때 헤더를 포함하는 모든 파일을 강제로 다시 컴파일해야합니다."이것이 가장 확실한 이유라고 생각합니다. 또한 헤더의 선언은 .c 파일의 구현보다 덜 자주 변경된다는 사실과 함께 진행됩니다.
Ninad

20

동료에게 알려주는 것은 대부분의 C ++ 코드를 최대한 활용하기 위해 템플릿해야한다는 개념입니다. 그리고 템플릿 화 된 경우 모든 것이 헤더 파일에 있어야 클라이언트 코드가이를보고 인스턴스화 할 수 있습니다. Boost와 STL에 충분하다면 우리에게 충분합니다.

나는이 견해에 동의하지 않지만 그것이 어디에서 왔는지에 대한 것입니다.


나는 당신이 이것에 대해 옳다고 생각합니다. 우리가 그것을 논의 할 때 그는 당신이 더 많거나 적은 경우 항상 템플릿의 예를 사용하고 있는 이 작업을 수행 할 수 있습니다. 나는 "해야 할 것"에도 동의하지 않지만 내 대안은 다소 복잡합니다.
TED

1
@ted-템플릿 코드의 경우 구현을 헤더에 넣어야합니다. 'export'키워드를 사용하면 컴파일러에서 템플릿의 선언 및 정의 분리를 지원할 수 있지만 내보내기 지원은 거의 존재하지 않습니다. anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1426.pdf
Michael Burr

헤더는, 그래,하지만 같은 헤더 필요는 없습니다. 아래의 알 수없는 답변을 참조하십시오.
TED

말이 되겠지만, 전에는 그 스타일을 본 적이 없다.
Michael Burr

14

나는 당신의 동료가 똑똑하고 당신도 정확하다고 생각합니다.

헤더에 모든 것을 넣는 것이 유용한 점은 다음과 같습니다.

  1. 헤더 및 소스를 작성 및 동기화 할 필요가 없습니다.

  2. 이 구조는 단순하며 순환 종속성이 없어서 코더가 "더 나은"구조를 만들도록 강요합니다.

  3. 이식성이 뛰어나 새 프로젝트에 쉽게 포함됩니다.

컴파일 시간 문제에 동의하지만 다음 사항에주의해야합니다.

  1. 소스 파일을 변경하면 헤더 파일이 변경되어 전체 프로젝트가 다시 컴파일됩니다.

  2. 컴파일 속도가 이전보다 훨씬 빠릅니다. 또한 오랜 시간 동안 고주파로 건설 할 프로젝트가있는 경우 프로젝트 설계에 결함이 있음을 나타낼 수 있습니다. 작업을 다른 프로젝트로 분리하면 모듈이이 문제를 피할 수 있습니다.

마지막으로 나는 단지 개인적인 관점에서 동료를 지원하고 싶습니다.


3
+1. 아무도 당신은 헤더에서 긴 컴파일 시간만이 너무 많은 의존성을 암시하여 나쁜 디자인이라고 암시 할 수 있다는 생각을 가지고있었습니다. 좋은 지적! 그러나 컴파일 시간이 실제로 짧은 정도로 이러한 종속성을 제거 할 수 있습니까?
TobiMcNamobi

@TobiMcNamobi : 나쁜 디자인 결정에 대한 더 나은 피드백을 얻기 위해 "느슨한"아이디어를 좋아합니다. 그러나 헤더 전용 대 개별 컴파일의 경우 그 아이디어를 결정하면 단일 컴파일 단위와 엄청난 컴파일 시간이 생깁니다. 디자인이 실제로 좋은 경우에도.
Jo So

다시 말해, 인터페이스와 구현의 분리는 실제로 설계의 일부입니다. C에서는 헤더와 구현을 분리하여 캡슐화에 대한 결정을 표현해야합니다.
Jo So

1
현대 언어와 마찬가지로 헤더를 완전히 삭제하는 데 전혀 단점이 있는지 궁금해졌습니다.
Jo So

12

종종 간단한 멤버 함수를 헤더 파일에 넣어 인라인 할 수 있습니다. 그러나 템플릿과 일관성을 유지하기 위해 코드 전체를 거기에 두려면? 일반 견과류입니다.

기억하십시오 : 어리석은 일관성은 작은 마음의 홉 고블린입니다 .


그래, 나도 그래 내가 사용하는 일반적인 규칙은 "한 줄의 코드에 맞으면 머리글에 두십시오"라는 줄을 따라있는 것 같습니다.
TED

라이브러리 A<B>가 cpp 파일에서 템플릿 클래스의 본문을 제공 한 후 사용자가 A<C>?
jww

@jww 명시 적으로 명시하지는 않았지만 템플릿 클래스는 헤더에 완전히 정의되어있어 컴파일러가 필요한 유형으로 인스턴스화 할 수 있습니다. 그것은 문체 선택이 아닌 기술 요구 사항입니다. 원래 질문의 문제는 누군가 템플릿에 적합한 지 결정하고 일반 수업에도 적합하다는 것입니다.
Mark Ransom

7

Tuomas가 말했듯이 헤더는 최소화되어야합니다. 완료하기 위해 조금 확장하겠습니다.

저는 개인적으로 C++프로젝트 에서 4 가지 유형의 파일을 사용 합니다.

  • 공공의:
  • 전달 헤더 : 템플릿 등의 경우이 파일은 헤더에 나타날 전달 선언을 가져옵니다.
  • 헤더 :이 파일에는 전달 헤더가 포함되어 있으며 공개 할 모든 것을 선언하고 클래스를 정의합니다 ...
  • 은밀한:
  • 개인 헤더 :이 파일은 구현을 위해 예약 된 헤더이며 헤더를 포함하고 도우미 함수 / 구조를 선언합니다 (예 : 술어 또는 술어). 불필요한 경우 건너 뛰십시오.
  • 소스 파일 : 개인 헤더 (또는 개인 헤더가없는 경우 헤더)를 포함하고 모든 것을 정의합니다 (템플릿이 아닌 ...)

또한 이것을 다른 규칙과 결합합니다. 선언 할 수있는 것을 정의하지 마십시오. 물론 나는 거기에서 합리적이지만 (Pimpl을 모든 곳에서 사용하는 것은 상당히 번거 롭습니다).

그것은 #include내가 그것을 벗어날 수있을 때마다 헤더 의 지시문 보다 전방 선언을 선호한다는 것을 의미 합니다.

마지막으로 가시성 규칙도 사용합니다. 심볼의 범위를 최대한 제한하여 외부 범위를 오염시키지 않습니다.

그것을 종합하면 :

// example_fwd.hpp
// Here necessary to forward declare the template class,
// you don't want people to declare them in case you wish to add
// another template symbol (with a default) later on
class MyClass;
template <class T> class MyClassT;

// example.hpp
#include "project/example_fwd.hpp"

// Those can't really be skipped
#include <string>
#include <vector>

#include "project/pimpl.hpp"

// Those can be forward declared easily
#include "project/foo_fwd.hpp"

namespace project { class Bar; }

namespace project
{
  class MyClass
  {
  public:
    struct Color // Limiting scope of enum
    {
      enum type { Red, Orange, Green };
    };
    typedef Color::type Color_t;

  public:
    MyClass(); // because of pimpl, I need to define the constructor

  private:
    struct Impl;
    pimpl<Impl> mImpl; // I won't describe pimpl here :p
  };

  template <class T> class MyClassT: public MyClass {};
} // namespace project

// example_impl.hpp (not visible to clients)
#include "project/example.hpp"
#include "project/bar.hpp"

template <class T> void check(MyClass<T> const& c) { }

// example.cpp
#include "example_impl.hpp"

// MyClass definition

의 경우에만 필요합니다 다음 생명의 은인 여기에 대부분의 시간은 앞으로 헤더가 쓸모 있다는 것이다 typedeftemplate때문에 구현 헤더입니다)


6

추가 할 수있는 더 많은 재미 추가하려면 .ipp(에 포함되는 템플릿 구현을 포함하는 파일을 .hpp하면서) .hpp인터페이스를 포함하고 있습니다.

템플릿 화 된 코드와는 별도로 (프로젝트에 따라 파일이 많거나 소수 일 수 있음) 정상적인 코드가 있으며 여기에서 선언과 정의를 분리하는 것이 좋습니다. 필요한 경우 앞으로 선언을 제공하십시오. 이는 컴파일 시간에 영향을 줄 수 있습니다.


템플릿 정의를 사용하여 수행 한 작업입니다 (동일한 확장을 사용했는지 확실하지 않지만 오랜 시간이 지났습니다).
TED

5

일반적으로 새 클래스를 작성할 때 클래스에 모든 코드를 넣을 것이므로 다른 파일을 찾아 볼 필요가 없습니다. 모든 작업이 완료된 후 메소드 본문을 cpp 파일로 나눕니다. 프로토 타입을 hpp 파일에 그대로 둡니다.


4

이 새로운 방식이 실제로 The Way 인 경우 프로젝트에서 다른 방향으로 진행했을 수 있습니다.

헤더에 불필요한 모든 것을 피하려고 노력하기 때문입니다. 헤더 캐스케이드를 피하는 것이 포함됩니다. 헤더의 코드에는 다른 헤더가 포함되어야하며 다른 헤더가 필요합니다. 템플릿을 사용해야하는 경우 템플릿이 너무 많은 헤더가 흩어지지 않도록하십시오.

또한 해당되는 경우 "불투명 포인터"패턴을 사용 합니다.

이러한 관행을 통해 대부분의 동료보다 더 빠르게 구축 할 수 있습니다. 그리고 그렇습니다. 코드 나 클래스 멤버를 변경해도 큰 재 구축이 발생하지 않습니다.


4

나는 개인적으로 헤더 파일 에서이 작업을 수행합니다.

// class-declaration

// inline-method-declarations

나는 물건을 빨리 찾는 것이 고통스럽기 때문에 메소드 코드를 클래스와 혼합하는 것을 좋아하지 않습니다.

헤더 파일에 모든 메소드를 넣지는 않습니다. 컴파일러는 (일반적으로) 가상 메소드를 인라인 할 수 없으며 루프없이 작은 메소드 만 인라인 할 수 있습니다 (전적으로 컴파일러에 따라 다름).

클래스에서 메소드를 수행하는 것은 유효하지만 readablilty 관점에서 나는 그것을 좋아하지 않습니다. 헤더에 메소드를 넣으면 가능하면 인라인됩니다.


2

IMHO, 그는 템플릿 및 / 또는 메타 프로그래밍을 수행하는 경우에만 가치가 있습니다. 헤더 파일을 선언으로 만 제한한다고 이미 언급 한 많은 이유가 있습니다. 그들은 단지 ... 헤더입니다. 코드를 포함하려면 코드를 라이브러리로 컴파일하고 링크하십시오.


2

모든 구현을 클래스 정의에서 제외했습니다. 클래스 정의에서 doxygen 주석을 사용하고 싶습니다.


1
늦었다는 것을 알고 있지만 다운 보터 (또는 공감 자)가 그 이유를 언급하려고합니까? 이것은 나에게 합리적인 진술처럼 보인다. 우리는 Doxygen을 사용하며 문제가 분명히 제기되었습니다.
TED

2

나는 모든 함수 정의를 헤더 파일에 넣는 것이 절대적으로 터무니없는 것이라고 생각합니다. 왜? 헤더 파일은 클래스에 대한 PUBLIC 인터페이스로 사용되기 때문입니다. "블랙 박스"외부입니다.

사용 방법을 참조하기 위해 클래스를 살펴 보려면 헤더 파일을 봐야합니다. 헤더 파일은 수행 할 수있는 작업 목록 (각 기능 사용 방법에 대한 자세한 설명을 제공하기 위해 제공)을 제공해야하며 멤버 변수 목록을 포함해야합니다. 불필요한 정보의 보트로드이고 헤더 파일을 복잡하게 만들기 때문에 각 개별 함수가 구현되는 방법을 포함하지 않아야합니다.


1

실제로 시스템의 복잡성과 사내 컨벤션에 의존하지 않습니까?

현재 저는 매우 복잡한 신경망 시뮬레이터에서 작업 중이며 사용 가능한 스타일은 다음과 같습니다.

classname.h의 클래스 정의 classnameCode.h의
클래스 코드 classname.cpp의
실행 코드

이것은 개발자가 만든 기본 클래스에서 사용자가 만든 시뮬레이션을 분할하고 상황에서 가장 잘 작동합니다.

그러나 사람들이 그래픽 응용 프로그램 또는 사용자에게 코드 기반을 제공하지 않는 다른 응용 프로그램 에서이 작업을 수행하는 것을보고 놀랐습니다.


1
"클래스 코드"와 "실행 가능 코드"의 차이점은 정확히 무엇입니까?
TED

내가 말했듯이, 그것은 신경 시뮬레이터입니다 : 사용자는 뉴런 등의 역할을하는 많은 클래스를 기반으로 실행 가능한 시뮬레이션을 만듭니다. 따라서 우리 코드는 실제로 아무것도 할 수없는 클래스이며 사용자는 실행 코드를 만듭니다. 시뮬레이터가 작업을 수행하게합니다.
Ed James

일반적으로, 대부분의 프로그램의 대다수 (전체가 아닌 경우)에 대해 "실제로 자체적으로 아무것도 할 수 없다"고 말할 수 없습니까? "메인"코드는 cpp에 들어가지만 다른 것은 없습니다.
TED

이 상황에서는 조금 다릅니다. 우리가 작성하는 코드는 기본적으로 라이브러리이며, 사용자는 실제로 실행할 수있는 시뮬레이션을 빌드합니다. openGL과 같이 생각하십시오-> 많은 함수와 객체를 얻지 만 cpp 파일이 없으면 쓸모가 없습니다.
Ed James

0

템플릿 코드는 헤더에만 있어야합니다. 그 외에도 인라인을 제외한 모든 정의는 .cpp에 있어야합니다. 이에 대한 가장 좋은 주장은 동일한 규칙을 따르는 std 라이브러리 구현입니다. std lib 개발자가 이것에 대해 옳다는 것에 동의하지 않을 것입니다.


어떤 stdlib? GCC libstdc++는 (AFAICS) 헤더에 있어야하는지 여부에 관계없이 src거의 모든 것을 거의 넣지 않는 것 같습니다 include. 그래서 나는 이것이 정확하고 유용한 인용이라고 생각하지 않습니다. 어쨌든, stdlibs 많은 사용자 코드에 대한 모델을 생각하지 않는다 : 그들은 분명히 고도로 숙련 된 코더에 의해 작성하고 있지만 수 사용 , 읽을 수 없습니다 : 그들은 추상적 멀리 높은 복잡성을 가장 코더에 대해 생각할 필요가 안된다 , _Reserved __names사용자와의 충돌을 피하기 위해 모든 곳에서 추악한 곳이 필요합니다 . 의견 및 간격은 내가 조언 한 것보다 낮습니다.
underscore_d

0

나는 동료가 헤더에 실행 코드를 작성하는 프로세스에 들어 가지 않는 한 옳다고 생각합니다. 올바른 균형은 .ads 파일이 사용자와 자녀를 위해 패키지의 인터페이스 정의를 완벽하게 제공하는 GNAT Ada가 나타내는 경로를 따르는 것입니다.

그런데 Ted는이 포럼에서 몇 년 전에 작성한 CLIPS 라이브러리에 바인딩하는 Ada에 대한 최근의 질문에 대해 살펴 보았으며 더 이상 사용할 수없는 웹 페이지가 닫혔습니다. 이전 Clips 버전으로 만들었더라도이 바인딩은 Ada 2012 프로그램 내에서 CLIPS 추론 엔진을 사용하려는 사람에게 좋은 시작 사례가 될 수 있습니다.


1
롤 2 년 후, 이것은 누군가를 붙잡는 이상한 방법입니다. 여전히 사본이 있는지 확인하지만 없을 것입니다. AI 클래스를 위해 Ada에서 코드를 만들 수 있었지만 CC0 (본질적으로 저작권이없는) 프로젝트를 의도적으로 만들었습니다.
TED
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.