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 변수 인스턴스들은 스레드가 종료되기 전에 소멸되지 않으므로 정적 변수의 소멸 순서 문제가 없다.
'CSE > 알쓸모' 카테고리의 다른 글
Google C++ Style Guide 정리하기 - Functions (0) | 2025.03.02 |
---|---|
Google C++ Style Guide 정리하기 - Classes (2) | 2025.03.01 |
어도비(adobe) 조기 취소 수수료 안내고 싶은데. (0) | 2025.02.28 |
Google C++ Style Guide 정리하기 - Header Files (0) | 2025.02.25 |
Google C++ Style Guide 정리하기 - 시작 (0) | 2025.02.24 |