C++ 코딩 표준 (C++ Core Guidelines)
C++ Core Guidelines에서 파생된 모던 C++(C++17/20/23)을 위한 포괄적인 코딩 표준입니다. 타입 안전성, 리소스 안전성, 불변성 및 명확성을 강조합니다.
사용 시점
- 새로운 C++ 코드(클래스, 함수, 템플릿)를 작성할 때
- 기존 C++ 코드를 리뷰하거나 리팩터링할 때
- C++ 프로젝트에서 아키텍처 결정을 내릴 때
- C++ 코드베이스 전체에서 일관된 스타일을 강제할 때
- 언어 기능(예:
enum vs enum class, 원시 포인터 vs 스마트 포인터)을 선택할 때
사용하지 않는 경우
- C++ 프로젝트가 아닌 경우
- 모던 C++ 기능을 채택할 수 없는 레거시 C 코드베이스인 경우
- 특정 가이드라인이 하드웨어 제약 조건과 충돌하는 임베디드/베어메탈 환경(선택적으로 조정하여 적용)
핵심 원칙
가이드라인 전체에 걸쳐 반복되는 주제이며 모든 기초가 됩니다.
- 모든 곳에 RAII 적용 (P.8, R.1, E.6, CP.20): 리소스 수명을 객체 수명에 바인딩하십시오.
- 기본적으로 불변성 유지 (P.10, Con.1-5, ES.25):
const/constexpr로 시작하십시오. 가변성(mutability)은 예외적인 경우여야 합니다.
- 타입 안전성 (P.4, I.4, ES.46-49, Enum.3): 타입 시스템을 사용하여 컴파일 타임에 에러를 방지하십시오.
- 의도 표현 (P.3, F.1, NL.1-2, T.10): 이름, 타입 및 개념은 목적을 전달해야 합니다.
- 복잡성 최소화 (F.2-3, ES.5, Per.4-5): 단순한 코드가 올바른 코드입니다.
- 포인터보다 값 의미론 선호 (C.10, R.3-5, F.20, CP.31): 값에 의한 반환과 스코프가 있는 객체를 선호하십시오.
철학 및 인터페이스 (Philosophy & Interfaces - P., I.)
주요 규칙
| 규칙 | 요약 |
|---|
| P.1 | 아이디어를 코드에서 직접 표현하십시오. |
| P.3 | 의도를 표현하십시오. |
| P.4 | 이상적으로, 프로그램은 정적으로 타입 안전해야 합니다. |
| P.5 | 런타임 체크보다 컴파일 타임 체크를 선호하십시오. |
| P.8 | 어떤 리소스도 유출하지 마십시오. |
| P.10 | 가변 데이터보다 불변 데이터를 선호하십시오. |
| I.1 | 인터페이스를 명시적으로 만드십시오. |
| I.2 | non-const 글로벌 변수를 피하십시오. |
| I.4 | 인터페이스를 정밀하고 강력한 타입으로 만드십시오. |
| I.11 | 원시 포인터나 참조로 소유권을 이전하지 마십시오. |
| I.23 | 함수 인자의 개수를 적게 유지하십시오. |
권장 사례 (DO)
// P.10 + I.4: 불변이며 강력한 타입의 인터페이스
struct Temperature {
double kelvin;
};
Temperature boil(const Temperature& water);
기피 사례 (DON'T)
// 약한 인터페이스: 소유권 불분명, 단위 불분명
double boil(double* temp);
// non-const 글로벌 변수
int g_counter = 0; // I.2 위반
함수 (Functions - F.*)
주요 규칙
| 규칙 | 요약 |
|---|
| F.1 | 의미 있는 작업은 신중하게 명명된 함수로 패키징하십시오. |
| F.2 | 함수는 하나의 논리적 작업만 수행해야 합니다. |
| F.3 | 함수를 짧고 단순하게 유지하십시오. |
| F.4 | 컴파일 타임에 평가될 수 있는 함수라면 constexpr로 선언하십시오. |
| F.6 | 예외를 던지지 않아야 하는 함수라면 noexcept로 선언하십시오. |
| F.8 | 순수 함수를 선호하십시오. |
| F.16 | "in" 인자의 경우, 복사 비용이 적은 타입은 값으로, 나머지는 const&로 전달하십시오. |
| F.20 | "out" 값의 경우, 출력 파라미터보다 반환 값을 선호하십시오. |
| F.21 | 여러 "out" 값을 반환하려면 구조체를 반환하는 것을 선호하십시오. |
| F.43 | 지역 객체에 대한 포인터나 참조를 절대 반환하지 마십시오. |
파라미터 전달
// F.16: 복사 비용이 적은 타입은 값으로, 나머지는 const&로
void print(int x); // 저렴함: 값으로
void analyze(const std::string& data); // 비쌈: const&로
void transform(std::string s); // Sink: 값으로 (이동될 것임)
// F.20 + F.21: 출력 파라미터가 아닌 반환 값 사용
struct ParseResult {
std::string token;
int position;
};
ParseResult parse(std::string_view input); // 좋음: 구조체 반환
// 나쁨: 출력 파라미터
void parse(std::string_view input,
std::string& token, int& pos); // 피할 것
순수 함수 및 constexpr
// F.4 + F.8: 가능한 경우 순수 함수, constexpr 사용
constexpr int factorial(int n) noexcept {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);
안티 패턴
- 함수에서
T&& 반환 (F.45)
va_arg / C 스타일 가변 인자 사용 (F.55)
- 다른 스레드에 전달되는 람다에서 참조로 캡처 (F.53)
- 이동 의미론을 방해하는
const T 반환 (F.49)
클래스 및 클래스 계층 구조 (Classes & Class Hierarchies - C.*)
주요 규칙
| 규칙 | 요약 |
|---|
| C.2 | 불변식이 존재하면 class를, 데이터 멤버가 독립적으로 변하면 struct를 사용하십시오. |
| C.9 | 멤버 노출을 최소화하십시오. |
| C.20 | 기본 연산 정의를 피할 수 있다면 피하십시오 (Rule of Zero). |
| C.21 | 복사/이동/소멸자 중 하나라도 정의하거나 =delete한다면 모두 처리하십시오 (Rule of Five). |
| C.35 | 베이스 클래스 소멸자: public virtual 또는 protected non-virtual로 만드십시오. |
| C.41 | 생성자는 완전히 초기화된 객체를 생성해야 합니다. |
| C.46 | 단일 인자 생성자는 explicit으로 선언하십시오. |
| C.67 | 다형성 클래스는 public 복사/이동을 억제해야 합니다. |
| C.128 | 가상 함수: virtual, override, final 중 정확히 하나만 지정하십시오. |
Rule of Zero
// C.20: 컴파일러가 특수 멤버를 생성하도록 둠
struct Employee {
std::string name;
std::string department;
int id;
// 소멸자, 복사/이동 생성자, 대입 연산자가 필요 없음
};
Rule of Five
// C.21: 리소스를 관리해야 한다면 5개 모두 정의
class Buffer {
public:
explicit Buffer(std::size_t size)
: data_(std::make_unique<char[]>(size)), size_(size) {}
~Buffer() = default;
Buffer(const Buffer& other)
: data_(std::make_unique<char[]>(other.size_)), size_(other.size_) {
std::copy_n(other.data_.get(), size_, data_.get());
}
Buffer& operator=(const Buffer& other) {
if (this != &other) {
auto new_data = std::make_unique<char[]>(other.size_);
std::copy_n(other.data_.get(), other.size_, new_data.get());
data_ = std::move(new_data);
size_ = other.size_;
}
return *this;
}
Buffer(Buffer&&) noexcept = default;
Buffer& operator=(Buffer&&) noexcept = default;
private:
std::unique_ptr<char[]> data_;
std::size_t size_;
};
클래스 계층 구조
// C.35 + C.128: 가상 소멸자, override 사용
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0; // C.121: 순수 인터페이스
};
class Circle : public Shape {
public:
explicit Circle(double r) : radius_(r) {}
double area() const override { return 3.14159 * radius_ * radius_; }
private:
double radius_;
};
안티 패턴
- 생성자/소멸자에서 가상 함수 호출 (C.82)
- non-trivial 타입에
memset/memcpy 사용 (C.90)
- 가상 함수와 오버라이더에 서로 다른 기본 인자 제공 (C.140)
- 데이터 멤버를
const나 참조로 만들어 이동/복사를 억제하는 행위 (C.12)
리소스 관리 (Resource Management - R.*)
주요 규칙
| 규칙 | 요약 |
|---|
| R.1 | RAII를 사용하여 리소스를 자동으로 관리하십시오. |
| R.3 | 원시 포인터(T*)는 소유권이 없는 것으로 간주합니다. |
| R.5 | 스코프가 있는 객체를 선호하십시오. 불필요하게 힙 할당을 하지 마십시오. |
| R.10 | malloc()/free()를 피하십시오. |
| R.11 | new와 delete를 명시적으로 호출하는 것을 피하십시오. |
| R.20 | 소유권을 나타낼 때는 unique_ptr 또는 shared_ptr을 사용하십시오. |
| R.21 | 소유권을 공유해야 하는 경우가 아니라면 shared_ptr보다 unique_ptr을 선호하십시오. |
| R.22 | shared_ptr을 만들 때는 make_shared()를 사용하십시오. |
스마트 포인터 사용
// R.11 + R.20 + R.21: 스마트 포인터를 이용한 RAII
auto widget = std::make_unique<Widget>("config"); // 단독 소유권
auto cache = std::make_shared<Cache>(1024); // 공유 소유권
// R.3: 원시 포인터 = 소유권 없는 관찰자
void render(const Widget* w) { // w를 소유하지 않음
if (w) w->draw();
}
render(widget.get());
RAII 패턴
// R.1: 리소스 획득은 초기화임
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: handle_(std::fopen(path.c_str(), "r")) {
if (!handle_) throw std::runtime_error("Failed to open: " + path);
}
~FileHandle() {
if (handle_) std::fclose(handle_);
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&& other) noexcept
: handle_(std::exchange(other.handle_, nullptr)) {}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (handle_) std::fclose(handle_);
handle_ = std::exchange(other.handle_, nullptr);
}
return *this;
}
private:
std::FILE* handle_;
};
안티 패턴
- Naked
new/delete 사용 (R.11)
- C++ 코드에서
malloc()/free() 사용 (R.10)
- 단일 표현식에서 여러 리소스 할당 (R.13 -- 예외 안전성 위험)
unique_ptr로 충분한 곳에 shared_ptr 사용 (R.21)
표현식 및 문장 (Expressions & Statements - ES.*)
주요 규칙
| 규칙 | 요약 |
|---|
| ES.5 | 스코프를 작게 유지하십시오. |
| ES.20 | 항상 객체를 초기화하십시오. |
| ES.23 | {} 초기화 구문을 선호하십시오. |
| ES.25 | 수정을 의도하지 않은 경우 객체를 const 또는 constexpr로 선언하십시오. |
| ES.28 | const 변수의 복잡한 초기화에는 람다를 사용하십시오. |
| ES.45 | 매직 상수를 피하고 상징적 상수를 사용하십시오. |
| ES.46 | 좁히기(narrowing)/손실이 발생하는 산술 변환을 피하십시오. |
| ES.47 | 0이나 NULL 대신 nullptr을 사용하십시오. |
| ES.48 | 캐스트를 피하십시오. |
| ES.50 | const를 캐스트로 제거하지 마십시오. |
초기화
// ES.20 + ES.23 + ES.25: 항상 초기화, {} 선호, 기본적으로 const
const int max_retries{3};
const std::string name{"widget"};
const std::vector<int> primes{2, 3, 5, 7, 11};
// ES.28: 복잡한 const 초기화를 위한 람다 사용
const auto config = [&] {
Config c;
c.timeout = std::chrono::seconds{30};
c.retries = max_retries;
c.verbose = debug_mode;
return c;
}();
안티 패턴
- 초기화되지 않은 변수 (ES.20)
- 포인터에
0이나 NULL 사용 (ES.47 -- nullptr 사용)
- C 스타일 캐스트 (ES.48 --
static_cast, const_cast 등 사용)
const 제거 (ES.50)
- 이름이 없는 매직 넘버 사용 (ES.45)
- 부호 있는 산술과 부호 없는 산술 혼용 (ES.100)
- 중첩된 스코프에서 이름 재사용 (ES.12)
에러 처리 (Error Handling - E.*)
주요 규칙
| 규칙 | 요약 |
|---|
| E.1 | 설계 초기 단계에서 에러 처리 전략을 개발하십시오. |
| E.2 | 함수가 할당된 작업을 수행할 수 없음을 알릴 때 예외를 던지십시오. |
| E.6 | 누수를 방지하기 위해 RAII를 사용하십시오. |
| E.12 | 예외를 던지는 것이 불가능하거나 허용되지 않는 경우 noexcept를 사용하십시오. |
| E.14 | 예외로 목적에 맞게 설계된 사용자 정의 타입을 사용하십시오. |
| E.15 | 값으로 던지고 참조로 잡으십시오 (Throw by value, catch by reference). |
| E.16 | 소멸자, 할당 해제 및 swap은 절대 실패해서는 안 됩니다. |
| E.17 | 모든 함수에서 모든 예외를 잡으려고 하지 마십시오. |
예외 계층 구조
// E.14 + E.15: 커스텀 예외 타입, 값으로 던지고 참조로 잡기
class AppError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class NetworkError : public AppError {
public:
NetworkError(const std::string& msg, int code)
: AppError(msg), status_code(code) {}
int status_code;
};
void fetch_data(const std::string& url) {
// E.2: 실패를 알리기 위해 예외를 던짐
throw NetworkError("connection refused", 503);
}
void run() {
try {
fetch_data("https://api.example.com");
} catch (const NetworkError& e) {
log_error(e.what(), e.status_code);
} catch (const AppError& e) {
log_error(e.what());
}
// E.17: 모든 것을 잡지 마십시오 -- 예상치 못한 에러는 전파되도록 둠
}
안티 패턴
int나 문자열 리터럴 같은 내장 타입 던지기 (E.14)
- 값으로 잡기 (슬라이싱 위험) (E.15)
- 에러를 조용히 삼키는 빈 catch 블록
- 흐름 제어를 위해 예외 사용 (E.3)
errno와 같은 글로벌 상태에 기반한 에러 처리 (E.28)
상수 및 불변성 (Constants & Immutability - Con.*)
모든 규칙
| 규칙 | 요약 |
|---|
| Con.1 | 기본적으로 객체를 불변으로 만드십시오. |
| Con.2 | 기본적으로 멤버 함수를 const로 만드십시오. |
| Con.3 | 기본적으로 포인터와 참조를 const로 전달하십시오. |
| Con.4 | 생성 후 변하지 않는 값에는 const를 사용하십시오. |
| Con.5 | 컴파일 타임에 계산 가능한 값에는 constexpr을 사용하십시오. |
// Con.1 ~ Con.5: 기본적으로 불변성 유지
class Sensor {
public:
explicit Sensor(std::string id) : id_(std::move(id)) {}
// Con.2: 기본적으로 const 멤버 함수
const std::string& id() const { return id_; }
double last_reading() const { return reading_; }
// 수정이 필요한 경우에만 non-const
void record(double value) { reading_ = value; }
private:
const std::string id_; // Con.4: 생성 후 절대 변하지 않음
double reading_{0.0};
};
// Con.3: const 참조로 전달
void display(const Sensor& s) {
std::cout << s.id() << ": " << s.last_reading() << '\n';
}
// Con.5: 컴파일 타임 상수
constexpr double PI = 3.14159265358979;
constexpr int MAX_SENSORS = 256;
동시성 및 병렬성 (Concurrency & Parallelism - CP.*)
주요 규칙
| 규칙 | 요약 |
|---|
| CP.2 | 데이터 레이스(data race)를 피하십시오. |
| CP.3 | 쓰기 가능한 데이터의 명시적 공유를 최소화하십시오. |
| CP.4 | 스레드가 아닌 작업(task) 관점에서 생각하십시오. |
| CP.8 | 동기화를 위해 volatile을 사용하지 마십시오. |
| CP.20 | RAII를 사용하십시오. 절대 lock()/unlock()을 직접 호출하지 마십시오. |
| CP.21 | 여러 뮤텍스를 획득할 때는 std::scoped_lock을 사용하십시오. |
| CP.22 | 락을 보유한 상태로 알 수 없는 코드를 호출하지 마십시오. |
| CP.42 | 조건 없이 대기하지 마십시오. |
| CP.44 | lock_guard와 unique_lock에 이름을 부여하는 것을 잊지 마십시오. |
| CP.100 | 반드시 필요한 경우가 아니라면 락 프리(lock-free) 프로그래밍을 하지 마십시오. |
안전한 락킹 (Safe Locking)
// CP.20 + CP.44: RAII 락, 항상 명명됨
class ThreadSafeQueue {
public:
void push(int value) {
std::lock_guard<std::mutex> lock(mutex_); // CP.44: 이름 부여!
queue_.push(value);
cv_.notify_one();
}
int pop() {
std::unique_lock<std::mutex> lock(mutex_);
// CP.42: 항상 조건과 함께 대기
cv_.wait(lock, [this] { return !queue_.empty(); });
const int value = queue_.front();
queue_.pop();
return value;
}
private:
std::mutex mutex_; // CP.50: 데이터와 함께 뮤텍스 배치
std::condition_variable cv_;
std::queue<int> queue_;
};
다중 뮤텍스
// CP.21: 여러 뮤텍스를 위한 std::scoped_lock (데드락 방지)
void transfer(Account& from, Account& to, double amount) {
std::scoped_lock lock(from.mutex_, to.mutex_);
from.balance_ -= amount;
to.balance_ += amount;
}
안티 패턴
- 동기화에
volatile 사용 (CP.8 -- 하드웨어 I/O 전용입니다)
- 스레드 분리(Detaching) (CP.26 -- 수명 관리가 거의 불가능해집니다)
- 이름 없는 락 가드:
std::lock_guard<std::mutex>(m);은 즉시 파괴됩니다 (CP.44)
- 콜백을 호출하는 동안 락 보유 (CP.22 -- 데드락 위험)
- 깊은 전문 지식 없이 락 프리 프로그래밍 시도 (CP.100)
템플릿 및 제네릭 프로그래밍 (Templates & Generic Programming - T.*)
주요 규칙
| 규칙 | 요약 |
|---|
| T.1 | 추상화 수준을 높이기 위해 템플릿을 사용하십시오. |
| T.2 | 여러 인자 타입에 대한 알고리즘을 표현하기 위해 템플릿을 사용하십시오. |
| T.10 | 모든 템플릿 인자에 대해 컨셉(concept)을 지정하십시오. |
| T.11 | 가능한 경우 표준 컨셉을 사용하십시오. |
| T.13 | 단순한 컨셉에는 약축 표기법을 선호하십시오. |
| T.43 | typedef보다 using을 선호하십시오. |
| T.120 | 정말 필요한 경우에만 템플릿 메타 프로그래밍을 사용하십시오. |
| T.144 | 함수 템플릿을 특수화하지 마십시오 (대신 오버로딩 사용). |
컨셉 (Concepts - C++20)
#include <concepts>
// T.10 + T.11: 표준 컨셉으로 템플릿 제약
template<std::integral T>
T gcd(T a, T b) {
while (b != 0) {
a = std::exchange(b, a % b);
}
return a;
}
// T.13: 단축 컨셉 구문
void sort(std::ranges::random_access_range auto& range) {
std::ranges::sort(range);
}
// 도메인 전용 제약을 위한 커스텀 컨셉
template<typename T>
concept Serializable = requires(const T& t) {
{ t.serialize() } -> std::convertible_to<std::string>;
};
template<Serializable T>
void save(const T& obj, const std::string& path);
안티 패턴
- 가시적인 네임스페이스에서 제약 없는 템플릿 사용 (T.47)
- 오버로딩 대신 함수 템플릿 특수화 사용 (T.144)
constexpr로 충분한 곳에 템플릿 메타 프로그래밍 사용 (T.120)
using 대신 typedef 사용 (T.43)
표준 라이브러리 (Standard Library - SL.*)
주요 규칙
| 규칙 | 요약 |
|---|
| SL.1 | 가능한 경우 라이브러리를 사용하십시오. |
| SL.2 | 다른 라이브러리보다 표준 라이브러리를 선호하십시오. |
| SL.con.1 | C 스타일 배열보다 std::array 또는 std::vector를 선호하십시오. |
| SL.con.2 | 기본적으로 std::vector를 선호하십시오. |
| SL.str.1 | 문자 시퀀스를 소유할 때는 std::string을 사용하십시오. |
| SL.str.2 | 문자 시퀀스를 참조할 때는 std::string_view를 사용하십시오. |
| SL.io.50 | endl을 피하십시오 ('\n' 사용 -- endl은 강제 플러시를 수행합니다). |
// SL.con.1 + SL.con.2: C 배열보다 vector/array 선호
const std::array<int, 4> fixed_data{1, 2, 3, 4};
std::vector<std::string> dynamic_data;
// SL.str.1 + SL.str.2: string은 소유, string_view는 관찰
std::string build_greeting(std::string_view name) {
return "Hello, " + std::string(name) + "!";
}
// SL.io.50: endl 대신 '\n' 사용
std::cout << "result: " << value << '\n';
열거형 (Enumerations - Enum.*)
주요 규칙
| 규칙 | 요약 |
|---|
| Enum.1 | 매크로보다 열거형을 선호하십시오. |
| Enum.3 | 일반 enum보다 enum class를 선호하십시오. |
| Enum.5 | 열거자 이름에 ALL_CAPS를 사용하지 마십시오. |
| Enum.6 | 익명 열거형을 피하십시오. |
// Enum.3 + Enum.5: 범위가 지정된 enum, ALL_CAPS 사용 안 함
enum class Color { red, green, blue };
enum class LogLevel { debug, info, warning, error };
// 나쁨: 일반 enum은 이름을 유출시키며, ALL_CAPS는 매크로와 충돌함
enum { RED, GREEN, BLUE }; // Enum.3 + Enum.5 + Enum.6 위반
#define MAX_SIZE 100 // Enum.1 위반 -- constexpr 사용
소스 파일 및 명명법 (Source Files & Naming - SF., NL.)
주요 규칙
| 규칙 | 요약 |
|---|
| SF.1 | 코드 파일에는 .cpp를, 인터페이스 파일에는 .h를 사용하십시오. |
| SF.7 | 헤더의 글로벌 스코프에서 using namespace를 쓰지 마십시오. |
| SF.8 | 모든 .h 파일에 #include 가드를 사용하십시오. |
| SF.11 | 헤더 파일은 자기 완비적(self-contained)이어야 합니다. |
| NL.5 | 이름에 타입 정보를 인코딩하지 마십시오 (헝가리안 표기법 금지). |
| NL.8 | 일관된 명명 스타일을 사용하십시오. |
| NL.9 | 매크로 이름에만 ALL_CAPS를 사용하십시오. |
| NL.10 | underscore_style 이름을 선호하십시오. |
헤더 가드
// SF.8: 인클루드 가드 (또는 #pragma once)
#ifndef PROJECT_MODULE_WIDGET_H
#define PROJECT_MODULE_WIDGET_H
// SF.11: 자기 완비적 -- 이 헤더가 필요한 모든 것을 포함함
#include <string>
#include <vector>
namespace project::module {
class Widget {
public:
explicit Widget(std::string name);
const std::string& name() const;
private:
std::string name_;
};
} // namespace project::module
#endif // PROJECT_MODULE_WIDGET_H
명명 컨벤션
// NL.8 + NL.10: 일관된 underscore_style
namespace my_project {
constexpr int max_buffer_size = 4096; // NL.9: ALL_CAPS 아님 (매크로가 아니므로)
class tcp_connection { // 클래스 이름도 underscore_style
public:
void send_message(std::string_view msg);
bool is_connected() const;
private:
std::string host_; // 멤버 변수에는 뒤에 언더스코어 추가
int port_;
};
} // namespace my_project
안티 패턴
- 헤더 글로벌 스코프에서
using namespace std; 사용 (SF.7)
- 인클루드 순서에 의존하는 헤더 (SF.10, SF.11)
strName, iCount 같은 헝가리안 표기법 (NL.5)
- 매크로가 아닌 것에 ALL_CAPS 사용 (NL.9)
성능 (Performance - Per.*)
주요 규칙
| 규칙 | 요약 |
|---|
| Per.1 | 근거 없이 최적화하지 마십시오. |
| Per.2 | 성급하게 최적화하지 마십시오. |
| Per.6 | 측정 없이 성능에 대해 주장하지 마십시오. |
| Per.7 | 최적화가 가능하도록 설계하십시오. |
| Per.10 | 정적 타입 시스템에 의존하십시오. |
| Per.11 | 계산을 런타임에서 컴파일 타임으로 옮기십시오. |
| Per.19 | 예측 가능하게 메모리에 접근하십시오. |
가이드라인
// Per.11: 가능한 경우 컴파일 타임 계산
constexpr auto lookup_table = [] {
std::array<int, 256> table{};
for (int i = 0; i < 256; ++i) {
table[i] = i * i;
}
return table;
}();
// Per.19: 캐시 친화적인 연속된 데이터 선호
std::vector<Point> points; // 좋음: 연속적임
std::vector<std::unique_ptr<Point>> indirect_points; // 나쁨: 포인터 추적(chasing) 발생
안티 패턴
- 프로파일링 데이터 없이 최적화 시도 (Per.1, Per.6)
- 명확한 추상화보다 "영리한" 저수준 코드 선택 (Per.4, Per.5)
- 데이터 레이아웃 및 캐시 동작 무시 (Per.19)
빠른 참조 체크리스트
C++ 작업을 완료로 표시하기 전 확인 사항: