콘월 이층집

headerfiles

Google C++ Style Guide 정리하기 - Scoping

반응형

Scoping

Namespaces

대부분의 코드가 합리적으로 짧은 이름을 사용할 수 있도록 하는 동시에 이름 충돌을 방지하는 방법을 제공한다.

  • 몇가지 예외를 제외하고는 네임스페이스에 코드를 배치한다
  • 가능한 프로젝트 이름과 경로를 기준으로 고유한 이름을 가져야 한다.
  • 아래 예제와 같이 주석을 달아 네임스페이스 종료를 알린다.
// In the .h file
namespace mynamespace {
 
// All declarations are within the namespace scope.
// Notice the lack of indentation.
class MyClass {
 public:
  ...
  void Foo();
};
 
}  // namespace mynamespace
// In the .cc file
namespace mynamespace {
 
// Definition of functions is within scope of the namespace.
void MyClass::Foo() {
  ...
}
 
}  // namespace mynamespace
  • inline namespace를 가급적 사용하지 않고, 꼭 사용해야한다면 아래의 예시처럼 default 인식에 주의한다.
    • 버전 관리같은 의도된 default 동작은 예외
    namespace CAR {
    namespace VER1 {
    class ElectricCar; // VER1 Class
    }  // namespace VER1
    
    inline namespace VER2 {
    class ElectricCar; // VER2 Class
    }  // namespace VER2
    
    class GasCar;
    
    }  // namepsace CAR
    
    // CAR::VER2::ElectricCar, VER2가 inline namespace이기 때문에 CAR namespace로 간주
    CAR::ElectricCar* car1;
    CAR::VER1::ElectricCar* car2;
    CAR::VER2::ElectricCar* car3;
    
  • using namespace 사용에 주의한다.
// Forbidden -- This pollutes the namespace.
using namespace foo;

// Shorten access to some commonly used names in .cc files.
namespace baz = ::foo::bar::baz;

 

Internal Linkage

함수, 변수 등이 해당 소스 파일(.cc)에서만 사용될 경우, 다른 파일에서 접근하지 못하도록 내부 연결을 부여한다.

  • Unnamed namespace 사용
namespace {
    int internalVar = 42;  // 다른 파일에서 접근 불가능
    void internalFunction() {
        // ...
    }
}  // namespace
  • static 키워드 사용
static int internalVar = 42;  // 다른 파일에서 접근 불가능
static void internalFunction() {
    // ...
}

내부 연결은 헤더 파일에서는 사용하 안된다.

  • 각각의 소스 파일에서 서로 다른 복사본이 생성되기 때문

Nonmember, Static Member, and Global Functions

비 멤버 함수와 정적 멤버 함수, 글로벌 함수의 적절한 사용이 필요하다.

  • 클래스에 속하지 않은 함수, Nonmember function 는 네임스페이스 안에 넣는다.
  • 글로벌 함수보다 네임스페이스를 활용한다.
  • 예시 1. 글로벌 네임스페이스의 오염
#include <iostream>

void PrintMessage() {  // 전역(global) 네임스페이스에 있음
    std::cout << "Hello, world!" << std::endl;
}
  • 예시 2. 올바른 예
#include <iostream>

namespace utils {
    void PrintMessage() {
        std::cout << "Hello, world!" << std::endl;
    }
}

int main() {
    utils::PrintMessage();
    return 0;
}

 

  • 정적 멤버 변수를 위한 클래스는 사용하지 않는다.
    • 예시 1. 클래스를 네임스페이스처럼 사용
class Utility {
public:
    static void PrintMessage() {
        std::cout << "Hello, world!" << std::endl;
    }
};

 

  • ODR(One Definition Rule) 문제 발생에 주의한다.
    • 같은 프로그램에서 동일한 이름의 함수가 여러 번 정의되면 컴파일 오류 발생
// file1.cpp
#include <iostream>
void PrintMessage() {  // 글로벌 함수
    std::cout << "Hello from file1!" << std::endl;
}

// file2.cpp
#include <iostream>
void PrintMessage() {  // 글로벌 함수 (중복 정의!)
    std::cout << "Hello from file2!" << std::endl;
}

// ❌ 링크 오류 발생! (ODR 위반)

Local Variables

가능한 가장 좁은 범위에 함수의 변수를 배치하고 선언에서 변수를 꼭 초기화한다.

  • C++ 에서는 함수의 어느 곳에서나 변수를 선언할 수 있다.
  • 처음 사용하는 곳에 가깝게 선언하는 것이 좋다.
    • 선언을 찾고 변수 유형과 초기화 대상을 쉽게 확인할 수 있음
    • 선언 및 할당 대신 초기화를 사용
      std::vector<int> v;
      v.push_back(1);  // Prefer initializing using brace initialization.
      v.push_back(2);
      
      std::vector<int> v = {1, 2};  // Good -- v starts initialized.
      
      std::vector<int> v;
      v.push_back(1);  // Prefer initializing using brace initialization.
      v.push_back(2);
      
      std::vector<int> v = {1, 2};  // Good -- v starts initialized.
  • If, while 및 for 문에 필요한 변수는 일반적으로 해당 statement 내에서 선언되어야 하며, 이러한 변수는 해당 범위로 제한된다.
while (const char* p = strchr(str, '/')) str = p + 1;
  • 변수가 객체인 경우, 해당 루프 외부에서 선언하는 것이 더 효율적이다.
// Inefficient implementation:
for (int i = 0; i < 1000000; ++i) {
  Foo f;  // My ctor and dtor get called 1000000 times each.
  f.DoSomething(i);
}

Foo f;  // My ctor and dtor get called once each.
for (int i = 0; i < 1000000; ++i) {
  f.DoSomething(i);
}

 

Static and Global Variable

정적 저장 수명(static storage duration)을 가진 객체는 프로그램이 시작될 때부터 종료될 때까지 존재하는 객체이다. 이러한 객체는 아래와 같은 경우에 생성된다.

  • 네임스페이스 수준의 전역 변수(global variable)
  • 클래스의 static 멤버 변수
  • 정적 함수 지역 변수(static function-local variable)
// 네임스페이스 스코프 (전역 변수)
const int globalValue = 42;  

// 클래스의 static 멤버 변수
class MyClass {
    static int classStaticVar;
};

// 함수 내부의 static 변수
void foo() {
    static int localStaticVar = 10;
}

이러한 변수들은 종료시 소멸되지만 여러 가지 문제를 유발할 수 있다.

  • 예시 1. 나쁜 예
// bad: non-trivial destructor
const std::string kFoo = "foo";

// Bad for the same reason, even though kBar is a reference (the
// rule also applies to lifetime-extended temporary objects).
const std::string& kBar = StrCat("a", "b", "c");

void bar() {
  // Bad: non-trivial destructor.
  static std::map<int, int> kData = {{1, 0}, {2, 0}, {3, 0}};
}
  • 예시 2. 좋은 예
const int kNum = 10;  // Allowed

struct X { int n; };
const X kX[] = {{1}, {2}, {3}};  // Allowed

void foo() {
  static const char* const kMessages[] = {"hello", "world"};  // Allowed
}

// Allowed: constexpr guarantees trivial destructor.
constexpr std::array<int, 3> kArray = {1, 2, 3};

thread_local Variables

thread_local 지정자를 사용하여 변수를 선언할 수 있다.

thread_local Foo foo = ...;

네임스페이스 범위, 함수 내부 또는 정적 클래스 멤버로 선언할 수 있지만 일반 클래스 멤버로는 선언할 수 없다.

  • 이러한 변수는 실제로 객체의 모임이므로 다른 스레드가 접근 할 때 실제로 다른 객체에 액세스 한다.
  • 변수 인스턴스는 프로그램 시작 시 한 번이 아니라 각 스레드에 대해 별도로 초기화되어야 한다는 점을 제외하고는 정적 변수와 매우 유사하게 초기화된다.
  • 즉, 함수 내에 선언된 thread_local 변수는 안전하지만 다른 thread_local 변수는 정적 변수(와 그 외)와 동일한 초기화 순서 문제의 영향을 받는다.
  • thread_local 변수 인스턴스들은 스레드가 종료되기 전에 소멸되지 않으므로 정적 변수의 소멸 순서 문제가 없다.
반응형

Google C++ Style Guide 정리하기 - Header Files

반응형

Header Files

일반적으로 모든 .cc 파일에는 연관된 .h 파일이 있어야 한다.

  • 단, 유닛 테스트 및 main() 함수만 포함하는 작은 .cc 파일과 같은 예외가 있음

헤더 파일을 올바르게 사용하면 코드의 가독성, 사이즈 및 성능 면에서 큰 차이를 가져올 수 있다.

Self-contained Headers

모든 헤더 파일은 독립적(self-contained)이어야 한다.

  • 여기서 독립적이란, abc.h, abc.cpp따로 파일을 만들어야 한다는 의미이다.
  • 단, template, inline function 에 대한 정의는 선언과 동일한 파일에 배치하는 걸 선호

The #define Guard

모든 헤더 파일에는 다중 포함을 방지하기 위해 #define 이 있어야 한다.

기호 이름의 형식은 PROJECT_PATH_FILE_H_ 여야 한다.

고유성을 보장하려면 프로젝트 소스 트리의 전체 경로를 기반으로 해야 한다.

예를 들어, foo 프로젝트의 foo/src/bar/baz.h 파일에는 아래와 같이 작성한다.

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

...

#endif // FOO_BAR_BAZ_H_

Include What You Use

소스 또는 헤더 파일이 다른 곳에 정의된 기호를 참조하는 경우, 파일에는 해당 기호의 선언 또는 정의를 적절하게 제공하려는 헤더 파일이 직접 포함되어야 한다.

즉, 전이적 포함(transitive inclusion)에 의존하면 안된다.

//foo.h
#include "bar.h"
class Foo 
{
public:
    Bar bar;
};
//bar.h
class Bar
{
};
//foo.cc
#include "foo.h"
// #include "bar.h"
int main()
{
    Foo* foo = new Foo();
    Bar* bar = new Bar();
}
  • foo.h 가 bar.h를 포함하기에 .cc 파일에서 foo.h 만 포함하려는 시도

Forward Declaration

가능하면 전방 선언을 사용하지 않는다.

  • 대신 필요한 헤더 파일을 포함해야 함
  • C++ 20 부터는 모듈 시스템의 도입으로 문제점이 완화되었음

Inline Functions

10줄 이하의 함수는 인라인으로 정의한다.

  • loop 또는 switch 의 경우 효율적이지 않음
  • 재귀 함수는 인라인으로 지정하지 않음

Names and Order of Includes

다음의 순서로 포함한다 :
관련 헤더, C 시스템 헤더, C++ 표준 라이브러리 헤더, 기타 라이브러리 헤더, 프로젝트 헤더
프로젝트의 모든 헤더 파일은 UNIX 디렉터리 별칭 .(현재 디렉터리) 또는 ..(상위 디렉터리)를 사용하지 않고 프로젝트 소스 디렉터리의 하위 항목으로 나열되어야 한다.

  • 예시 1. google-awesome-project/src/base/logging.h 는 다음과 같이 포함되어야 한다.

    #include "base/logging.h"
  • 예시 2. google-awesome-project/src/foo/internal/fooserver.cc는 다음과 같을 수 있다:

    #include "foo/server/fooserver.h"
    
    #include <sys/types.h>
    #include <unistd.h>
    
    #include <string>
    #include <vector>
    
    #include "base/basictypes.h"
    #include "base/commandlineflags.h"
    #include "foo/server/bar.h"
  • 예외 1. 조건부 포함

    #include "foo/public/fooserver.h"
    
    #include "base/port.h"  // For LANG_CXX11.
    
    #ifdef LANG_CXX11
    #include <initializer_list>
    #endif  // LANG_CXX11
반응형

+ 최근 글