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 변수 인스턴스들은 스레드가 종료되기 전에 소멸되지 않으므로 정적 변수의 소멸 순서 문제가 없다.

+ Recent posts