source

대형 프로젝트에서 선호하는 C/C++ 헤더 정책은 무엇입니까?

goodcode 2022. 7. 31. 23:01
반응형

대형 프로젝트에서 선호하는 C/C++ 헤더 정책은 무엇입니까?

큰 C/C++ 프로젝트에서 작업할 때 소스 파일 또는 헤더 파일에 #포함하는 것과 관련된 특정 규칙이 있습니까?

예를 들어 다음 두 가지 과도한 규칙 중 하나를 따를 수 있습니다.

  1. .h 파일에서는 #displaces는 금지되어 있습니다.필요한 헤더를 모두 포함하는 것은 각 .c 파일에 달려 있습니다.
  2. 각 .h 파일은 모든 종속성을 포함해야 합니다. 즉, 오류 없이 단독으로 컴파일할 수 있어야 합니다.

어떤 프로젝트든 그 중간에는 트레이드오프가 있을 것 같은데, 당신은 어떤 프로젝트입니까?좀 더 구체적인 규칙이 있나요?아니면 어떤 해결책에 대한 어떤 링크라도 있습니까?

H 파일을 C 파일에만 포함할 경우 H 파일을 C 파일에 포함하면 컴파일이 실패할 수 있습니다.20개의 다른 H-파일을 사전에 포함해야 할 수도 있고, 더 나쁜 것은 올바른 순서로 포함시켜야 하기 때문에 실패할 수 있습니다.H파일이 많으면 이 시스템은 장기적으로 관리상의 악몽이 됩니다.H 파일 하나만 포함하면 다른 H 파일도 어떤 순서로 포함시켜야 하는지 2시간 동안 확인할 수 있습니다.

다른 H 파일이 먼저 포함된 경우에만 H 파일을 C 파일에 성공적으로 포함할 수 있는 경우 첫 번째 H 파일은 두 번째 H 파일을 포함해야 합니다.이렇게 하면 컴파일이 깨질까 봐 걱정할 필요 없이 원하는 모든 C파일에 모든 H파일을 포함할 수 있습니다.이렇게 하면 직접 종속성만 지정할 수 있습니다. 그러나 이러한 종속성 자체에도 종속성이 있는 경우, 이러한 종속성을 지정하는 것은 사용자에게 달려 있습니다.

한편, 필요 없는 경우는, H파일을 H파일에 포함하지 말아 주세요. hashtable.h에는 해시 테이블 구현을 사용하기 위해 필요한 다른 헤더 파일만 포함해야 합니다.에 「」가 필요한 .hashing.h.hashtable.c에.hashtable.h최종 해시 테이블만 사용하는 코드가 아니라 구현에만 필요한 코드입니다.

나는 제안된 두 가지 규칙 모두 나쁘다고 생각한다.저는 항상 다음과 같이 신청합니다.

이 헤더에 정의된 것만 사용하여 파일을 컴파일하는 데 필요한 헤더 파일만 포함합니다.이것은 다음을 의미합니다.

  1. 참조 또는 포인터로만 존재하는 모든 개체는 앞으로 선언해야 합니다.
  2. 헤더 자체에 사용되는 함수 또는 개체를 정의하는 모든 헤더를 포함합니다.

규칙 2를 사용합니다.

모든 헤더는 다음과 같은 경우에 자급자족해야 합니다.

  • 다른 곳에서 정의된 것을 사용하지 않음
  • 다른 곳에 정의된 선언 기호 전달
  • 앞으로 이동할 수 없는 기호를 정의하는 헤더를 포함합니다.

따라서 빈 C/C++ 소스 파일이 있는 경우 헤더를 포함하여 올바르게 컴파일해야 합니다.

그런 다음 C/C++ 소스 파일에 필요한 것만 포함합니다.헤더 A가 헤더 B에 정의된 기호를 포워드 선언한 경우, 이 기호를 사용하는 경우 두 가지 모두 포함해야 합니다.좋은 소식은 앞으로 선언된 기호를 사용하지 않으면 Header A만 포함할 수 있고 Header B는 포함하지 않을 수 있다는 것입니다.

템플릿을 사용하면 이 검증이 "헤더를 포함한 빈 소스가 컴파일되어야 한다"는 점에 유의하십시오(그리고 재미있는...).

순환 종속성이 있는 즉시 첫 번째 규칙이 실패합니다.그래서 엄격하게 적용할 수 없습니다.

(이것은 아직 유효하게 할 수 있지만, 이것은 많은 작업을 프로그래머로부터 이러한 라이브러리의 소비자로 옮깁니다.이것은 명백히 잘못된 것입니다.)

룰 합니다(「의 「포워드 선언 헤더, 「2의 「 선언 헤더」를 실거래가 아닌 「포워드 선언 헤더」를 포함시키는 도 모릅니다).<iosfwd>일반적으로 헤더 파일에 어떤 의존관계가 있는지, 필요한 파일을 포함하는 것보다 더 나은 방법이 있다면 이는 일종의 자기 문서화라고 생각합니다.

편집:

코멘트에서는, 헤더간의 순환 의존성은 디자인이 나쁘다는 것을 나타내는 것이므로, 피해야 한다고 생각하고 있습니다.

그건 옳지 않아요.사실, 클래스 의 순환 의존성은 피할 수 없을 수도 있고 설계가 나쁘다는 징후는 전혀 아닙니다.예를 들어, 관찰자와 피험자 사이에 순환 참조가 있는 관찰자 패턴을 언급하겠습니다.

C++에서는 선언 순서가 중요하기 때문에 클래스 간의 순환성을 해결하려면 순방향 선언을 사용해야 합니다.이 순방향 선언을 순환적으로 처리하여 전체 파일 수를 줄이고 코드를 중앙 집중화하는 것이 완전히 허용됩니다.물론, 이 시나리오에서는 전진 선언이 1개뿐이기 때문에 다음과 같은 경우는 의미가 없습니다.하지만, 저는 도서관에서 일해본 적이 있습니다.

// observer.hpp

class Observer; // Forward declaration.

#ifndef MYLIB_OBSERVER_HPP
#define MYLIB_OBSERVER_HPP

#include "subject.hpp"

struct Observer {
    virtual ~Observer() = 0;
    virtual void Update(Subject* subject) = 0;
};

#endif

// subject.hpp
#include <list>

struct Subject; // Forward declaration.

#ifndef MYLIB_SUBJECT_HPP
#define MYLIB_SUBJECT_HPP

#include "observer.hpp"

struct Subject {
    virtual ~Subject() = 0;
    void Attach(Observer* observer);
    void Detach(Observer* observer);
    void Notify();

private:
    std::list<Observer*> m_Observers;
};

#endif

2.h 파일의 최소 버전에는 컴파일에 특별히 필요한 헤더 파일만 포함되며, 가능한 한 포워드 선언과 pimpl을 사용합니다.

  1. 항상 머리글 가드가 있어야지
  2. .using namespace명령어를 지정합니다.

두 번째 선택지로 가는 것을 추천합니다.헤더 파일에 갑자기 다른 헤더파일이 필요한 경우가 많습니다.첫 번째 옵션에서는 많은 C파일을 갱신해야 합니다.경우에 따라서는 사용자가 관리할 수 없는 경우도 있습니다.두 번째 옵션에서는 헤더 파일을 업데이트하기만 하면 됩니다.또, 추가한 신기능조차 필요 없는 유저는, 자신이 그것을 실행했는지조차 알 필요가 없습니다.

번째 대체 (no 첫첫첫아 ( no ) )#includes headers)는major .s in headers)의 no입니다.롭게 하고 #include한 하지 않고#include을 사용하다그래서 저는 일반적으로 두 번째 규칙을 따릅니다.

순환 의존성에 대해서는 클래스보다는 모듈로 프로젝트를 구성하는 것이 개인 해결책입니다.모듈 내에서는 모든 유형 및 함수가 서로 임의 종속성을 가질 수 있습니다.모듈 경계에 걸쳐 모듈 간에 순환 의존성이 없을 수 있습니다.각 모듈에는 *.hpp 파일1개와 *.cpp 파일1개가 있습니다.이것에 의해, 헤더내의 모든 전송 선언(모듈내에서만 발생할 수 있는 순환 의존성에 필요)은, 최종적으로 항상 같은 헤더내에서 해결됩니다.전송 선언 전용 헤더는 전혀 필요하지 않습니다.

예를 들어 Visual Studio에서 StdAfx.h의 용도는 다음과 같습니다.모든 공통 헤더를 거기에 배치하면...

이것은 인터페이스 설계로 귀결됩니다.

  1. 항상 참조 또는 포인터로 통과합니다.포인터를 체크하지 않을 경우 참조로 패스합니다.
  2. 가능한 한 전진 신고해 주세요.
  3. 클래스에서 신규로 사용하지 마십시오.공장을 생성하여 이를 클래스에게 전달합니다.
  4. 미리 컴파일된 헤더는 사용하지 마십시오.

Windows에서 stdafx에는 afx_.h 헤더만 포함되어 있습니다.문자열, 벡터 또는 부스트 라이브러리는 포함되지 않습니다.

규칙 nr. 1에서는 헤더 파일을 매우 구체적인 순서로 나열해야 합니다(파생 클래스의 파일을 포함하기 전에 먼저 해야 하는 기본 클래스의 파일 포함 등). 순서를 잘못 지정하면 컴파일 오류가 발생하기 쉽습니다.

요령은 다른 여러 사람이 언급했듯이 가능한 한 정방향 선언을 사용하는 것입니다. 즉, 참조나 포인터가 사용되는 경우입니다.이러한 방법으로 빌드 의존성을 최소화하기 위해 pimpl 관용구를 사용할 수 있습니다.

Mecki의 의견에 동의해, 짧게 말하면,

프로젝트의 모든 foo.h에 대해 필요한 헤더만 포함합니다.

// foo.c
#include "any header"
// end of foo.c

컴파일하다

(미리 컴파일된 헤더를 사용하는 경우는 물론 허용됩니다.예를 들어 MSVC의 #include "stdafx.h" 등)

개인적으로는 다음과 같이 합니다.
1 다른 .h 파일을 .h 파일에 포함하도록 perfer forward 선언합니다.그 .h 파일이나 클래스에서 포인터/참조로 사용할 수 있는 것이 있으면 컴파일 오류 없이 포워드 선언이 가능합니다.이로 인해 헤더가 종속성을 덜 포함할 수 있습니다(컴파일 시간 절약).불명확: ( )
2 .h 파일을 단순하거나 특정하게 만듭니다.예를 들어 CONST.h라는 파일에 모든 Constance를 정의하는 것은 좋지 않습니다.예를 들어 CONST_NETWORK.h, CONST_DB.h와 같이 여러 개의 Constance로 분할하는 것이 좋습니다.따라서 DB의 1개의 Constance를 사용하기 위해서는 네트워크에 대한 다른 정보를 포함할 필요가 없습니다.
3 구현은 헤더에 넣지 마십시오.헤더는 다른 사람을 위해 공개적인 것을 빠르게 검토하기 위해 사용됩니다. 이를 구현할 때 다른 사람을 위해 세부 사항으로 선언을 오염시키지 마십시오.

언급URL : https://stackoverflow.com/questions/181921/your-preferred-c-c-header-policy-for-big-projects

반응형