C++ 이디엄
C++ 이디엄이 특히 중요한 이유는 언어가 raw pointer, manual memory, 예외, 다중 상속, 템플릿 메타프로그래밍을 모두 허용하기 때문이다. 컴파일러가 강제하지 않는 invariant를 코드 형태로 인코딩하는 community convention이 사실상 안전 경계 역할을 한다. Rust의 borrow checker처럼 도구가 강제하는 게 아니라 사람이 유지해야 한다는 점이 핵심.
개인 메모: 중국에서는 C++ 위주로 돌아가는 경향이 있다.
RAII (Resource Acquisition Is Initialization)
리소스 획득을 객체 수명에 묶는 이디엄. 생성자에서 획득, 소멸자에서 해제. C++ 안전성의 토대.1
해결하는 문제: 예외, early return, break 등 어떤 경로로 스코프를 빠져나가도 리소스가 누수되지 않는다. C의 goto cleanup 패턴을 컴파일러가 자동으로 처리해주는 셈.
{
std::lock_guard<std::mutex> lock(mtx); // 획득
// 예외가 던져져도, return 해도, 정상 종료해도
// 스코프 종료 시점에 lock이 자동 해제
}
대표 예시: std::lock_guard, std::unique_ptr, std::fstream, std::scoped_lock.
Rule of Zero / Three / Five
리소스를 소유하는 클래스의 special member function(소멸자, 복사 생성자, 복사 대입, 이동 생성자, 이동 대입)을 어떻게 다룰지에 대한 메타 이디엄. Rule of Three는 1991년 Marshall Cline이 처음 정리했고, C++11에서 이동 연산이 추가되며 Rule of Five로 확장되었다.2
Rule of Zero (가장 권장): 직접 special member function을 작성하지 말고 RAII 컴포넌트에 위임. 컴파일러 생성 버전이 알아서 한다.
Rule of Three (C++98): 소멸자, 복사 생성자, 복사 대입 중 하나를 정의했다면 셋 다 정의해야 한다.
Rule of Five (C++11): 위 셋에 이동 생성자, 이동 대입 연산자까지 다섯 개를 함께 고려.
해결하는 문제: 리소스 소유 클래스가 복사/이동될 때 이중 해제, 누수, 부분 상태 발생.
// Rule of Zero - 권장
class Buffer
{
std::vector<int> data; // vector가 알아서 복사/이동/소멸 처리
std::string name;
// special member 함수 작성 안 함
};
// Rule of Five - 직접 OS 리소스를 잡을 때만
class FileHandle
{
public:
explicit FileHandle(const char* path);
~FileHandle(); // (1)
FileHandle(const FileHandle&) = delete; // (2) 복사 금지
FileHandle& operator=(const FileHandle&) = delete; // (3)
FileHandle(FileHandle&&) noexcept; // (4)
FileHandle& operator=(FileHandle&&) noexcept; // (5)
private:
int fd;
};
Smart pointer ownership
소유권을 type으로 표현. raw pointer는 기본적으로 non-owning 관찰자로 사용한다.3
std::unique_ptr<T>: 단일 소유. 이동만 가능, 복사 불가. 비용 거의 0.std::shared_ptr<T>: 공유 소유. control block의 참조 카운트로 마지막 소유자가 사라질 때 해제한다. 복사/소멸 시 참조 카운트 조작에 동기화 비용이 발생한다. 카운트만 atomic이지 가리키는 객체 자체가 thread-safe해지는 건 아니다.std::weak_ptr<T>:shared_ptr의 비소유 관찰자. lifetime을 연장하지 않는다 (strong count 안 늘림). 다만 control block은 weak count가 0이 될 때까지 해제되지 않으므로 완전한 무비용은 아니다. 순환 참조 끊기 용도.
해결하는 문제: "이 포인터를 누가 delete 해야 하는가"라는 ownership 모호성. raw pointer는 함수 시그니처만 봐서는 소유인지 관찰인지 알 수 없다.
auto conn = std::make_unique<Connection>(host, port); // 단일 소유
auto cfg = std::make_shared<Config>(); // 공유 소유
std::weak_ptr<Config> watcher = cfg; // 관찰만, strong count 증가 없음
void use(Connection* c); // non-owning 인자 — 애플리케이션 코드에서 raw pointer의 가장 일반적인 정당한 용법
use(conn.get());
선택 순서: unique_ptr → 공유 의미가 진짜 필요할 때만 shared_ptr → non-owning 참조는 raw pointer 또는 T&.
raw pointer를 non-owning view로 쓰는 게 원칙이라는 건 애플리케이션 코드 기준이다. 다음 경계에서는 raw pointer가 소유나 그 외 용도로 정당하게 쓰인다:
C API boundary (
malloc/free, OS handle)arena/pool allocator 내부
intrusive data structure
placement new
레거시 ABI 호환
allocation-free 임베디드/실시간 영역
개인 메모: 현장에서는 RAII와 소유권 모델보다 raw pointer 신앙이 더 강하다.
C++의 난이도는 문법이 아니라 사람에게서 온다.
copy-and-swap
복사 대입 연산자를 "값 전달로 복사본을 받고 swap"으로 구현하는 이디엄. Herb Sutter의 Guru of the Week #59(1999)에서 정리된 표현이 가장 널리 인용된다.4
해결하는 문제: 예외 안전성 + 자기 대입(a = a) 처리를 한 번에. 대입 도중 예외가 나도 객체가 부분 상태로 깨지지 않는다. 새 복사본 생성이 실패하면 원본은 그대로 유지된다.
// 비-소유 멤버를 쓰면 Rule of Zero가 알아서 처리한다.
// 예제 단순화를 위해 vector를 사용 — raw pointer로 소유했다면
// Rule of Five의 다섯 함수가 모두 필요하다는 점을 잊지 말 것.
class Resource
{
public:
Resource& operator=(Resource other) // ← by value
{
swap(*this, other);
return *this;
}
friend void swap(Resource& a, Resource& b) noexcept
{
using std::swap;
swap(a.data, b.data);
swap(a.name, b.name);
}
private:
std::vector<int> data;
std::string name;
};
트레이드오프: copy-and-swap은 강한 예외 안전성을 보장하지만 항상 임시 객체를 만들기 때문에 기존 저장소를 재사용하기 어렵다. C++11 이후 by-value assignment 하나로 복사 대입과 이동 대입을 어느 정도 통합할 수는 있다 — rvalue가 들어오면 move constructor가 other를 만들기 때문이다. 다만 성능에 민감한 컨테이너/버퍼 타입은 복사 대입과 이동 대입을 별도로 구현하고, 이동 대입은 swap만 수행하는 편이 효율적이다.
2. 캡슐화와 인터페이스 설계
Pimpl (Pointer to Implementation)
클래스 본체에는 구현 세부사항을 forward 선언된 포인터로만 두고, 실제 구현은 .cpp에 정의. "Cheshire Cat" 또는 "compilation firewall"이라고도 부른다. Herb Sutter의 GotW #24와 C++11 갱신판 GotW #100/#101이 가장 자주 인용되는 출처다.5
해결하는 문제: 헤더 변경으로 인한 재컴파일 전파 차단, ABI 안정성, 사설 의존 헤더의 클라이언트 노출 방지.
// widget.h
class Widget
{
public:
Widget();
~Widget();
void draw();
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
private:
struct Impl; // forward 선언만
std::unique_ptr<Impl> impl;
};
// widget.cpp
struct Widget::Impl
{
Renderer renderer; // 실제 멤버
State state;
};
Widget::Widget() : impl(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // Impl 완전 정의 시점에 정의
비용: 동적 할당 한 번, 간접 호출 한 번. 핫 경로엔 부적합.
주의 1: 소멸자, 이동 연산을 헤더에 = default로 두면 안 된다 — Impl이 incomplete type인 시점에 unique_ptr이 인스턴스화되면서 에러. .cpp에서 정의해야 한다. 흔히 놓치는 함정이라 별표.
주의 2: std::unique_ptr<Impl>을 멤버로 두면 컴파일러가 생성하는 복사 생성자/복사 대입은 자동으로 삭제된다(unique_ptr이 복사 불가). Pimpl 객체가 복사 가능해야 한다면 복사 생성자와 복사 대입을 직접 정의하여 Impl을 deep copy해야 한다.
NVI (Non-Virtual Interface)
public 인터페이스는 non-virtual, 실제 다형성은 private virtual로 구현하는 이디엄. Herb Sutter가 2001년 C/C++ Users Journal의 "Virtuality" 글에서 명시적으로 권장한 이후 표준 가이드로 자리잡았다.6
해결하는 문제: 인터페이스와 동작 분리. 기반 클래스가 사전/사후 조건, 인자 검증, 로깅 같은 횡단 관심사를 강제할 수 있다. 파생 클래스는 동작만 구현.
class Document
{
public:
virtual ~Document() = default; // polymorphic base는 virtual destructor 필수
void save() // public non-virtual
{
validate();
do_save(); // 다형성 지점
log_saved();
}
private:
virtual void do_save() = 0; // 파생 클래스가 구현할 부분
void validate() const;
void log_saved() const;
};
기반 클래스 포인터/참조로 파생 객체를 다루는 polymorphic base 클래스는 항상 virtual destructor를 명시한다. 그렇지 않으면 delete base_ptr 시점에 파생 클래스의 소멸자가 호출되지 않는다.
3. 타입 수준 표현
std::optional로 부재 표현
값이 있을 수도 없을 수도 있는 경우 nullptr이나 sentinel value(-1, 빈 문자열 등) 대신 std::optional<T>를 사용.7
해결하는 문제: nullptr dereference, "음수면 실패" 같은 ad-hoc 컨벤션, 함수 시그니처가 실패 가능성을 숨기는 문제.
std::optional<User> find_user(UserId id)
{
if (auto record = db.lookup(id)) {
return User(*record);
}
return std::nullopt;
}
if (auto user = find_user(id)) {
process(*user); // 안에서 *user 또는 user->method()
}
트레이드오프: 실패 이유가 여러 개라면 std::expected<T, E> (C++23) 또는 Result 계열 타입이 더 적절. optional은 "있다/없다" 두 상태만.
주의: std::optional<T&>는 C++23 시점에도 표준에 포함되지 않는다(C++26 제안 단계). 참조에는 std::optional<std::reference_wrapper<T>> 또는 raw pointer T*를 쓴다.
std::variant + std::visit로 합 타입 표현
여러 타입 중 하나를 담는 type-safe union. std::visit으로 패턴 매칭에 가까운 dispatch.8
해결하는 문제: 상속 계층 없이 닫힌 타입 집합을 표현. dynamic_cast 회피. overload set을 쓰면 컴파일러가 모든 alternative에 대해 처리 가능성을 검증할 수 있다.
using Shape = std::variant<Circle, Square, Triangle>;
double area(const Shape& s)
{
return std::visit([](const auto& shape) {
return shape.area(); // 모든 타입에 area()가 있어야 함
}, s);
}
// overload set으로 타입별 처리
template<class... Ts>
struct Overloaded : Ts...
{
using Ts::operator()...;
};
template<class... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;
double area2(const Shape& s)
{
return std::visit(Overloaded {
[](const Circle& c) { return 3.14 * c.r * c.r; },
[](const Square& q) { return q.side * q.side; },
[](const Triangle& t) { return 0.5 * t.base * t.height; }
}, s);
}
exhaustiveness에 관한 주의: 위 두 예시는 강제 수준이 다르다.
area의 generic lambda는 "모든 alternative가area()를 호출 가능해야 한다"는 것만 강제한다. 타입별 명시적 처리는 강제하지 않는다.area2의 overload set은 alternative마다 매칭되는 오버로드가 있어야 컴파일이 통과한다. 닫힌 타입 집합에 대해 누락된 케이스를 컴파일 타임에 드러낼 수 있다. 단, generic lambda(auto)나 catch-all 오버로드를 섞으면 exhaustiveness가 약해진다.
트레이드오프: 컴파일 시간 증가. 타입 집합이 자주 확장되는 도메인이라면 virtual function 계층이 더 유연.
4. 템플릿과 컴파일 타임 이디엄
CRTP (Curiously Recurring Template Pattern)
파생 클래스가 자기 자신을 템플릿 인자로 기반 클래스에 전달하는 패턴. James Coplien이 1995년 C++ Report에서 초기 C++ 템플릿 코드의 반복 패턴을 관찰하고 명명했다.9 정적 다형성, mixin, 정책 주입에 사용.
해결하는 문제: virtual function의 런타임 비용(vtable lookup) 없이 인터페이스 재사용. 정적 분기. 그리고 기반 클래스가 파생 클래스에 코드를 "주입"하는 mixin 효과.
template<typename Derived>
class Comparable
{
public:
bool operator!=(const Derived& other) const
{
return !static_cast<const Derived&>(*this).operator==(other);
}
bool operator<=(const Derived& other) const
{
const auto& self = static_cast<const Derived&>(*this);
return self < other || self == other;
}
};
class Point : public Comparable<Point>
{
public:
bool operator==(const Point& o) const { return x == o.x && y == o.y; }
bool operator<(const Point& o) const { return x < o.x || (x == o.x && y < o.y); }
private:
int x, y;
};
비용: 인터페이스가 컴파일 타임에 고정. 런타임에 타입을 바꿔야 하는 다형성에는 부적합. 코드 비대 가능성.
C++20 concepts와의 관계: CRTP가 인터페이스 conformance를 체크하는 용도(파생 타입이 특정 연산을 제공한다는 사실의 표현)로 쓰였다면 concepts가 훨씬 깔끔하게 대체한다. 다만 위 Comparable<Point> 예시처럼 기반 클래스가 파생 클래스에 메서드를 주입하는 mixin 용도는 concepts로 대체되지 않는다. 이 부분은 여전히 CRTP 또는 다른 합성 기법이 필요하다.
Type erasure
구체 타입을 숨기고 인터페이스만 노출하는 이디엄. 내부에는 가상 함수 또는 함수 포인터 테이블을 두고 외부로는 타입 정보를 흘리지 않는다. Sean Parent의 GoingNative 2013 발표 "Inheritance Is The Base Class of Evil"이 value semantics 기반 type erasure를 대중화시킨 분기점으로 자주 인용된다.10
해결하는 문제: 템플릿 없이 다형적 컨테이너 표현, 헤더 의존성 축소, ABI 안정성.
// 표준 라이브러리의 type erasure 예
std::function<int(int)> f = [](int x) { return x * 2; };
f = some_free_function; // 시그니처만 맞으면 어떤 호출 가능 객체도 OK
std::any value = 42;
value = std::string("hello"); // 어떤 타입이든 담김
대표 사례: std::function, std::any, std::shared_ptr의 deleter.
비용: heap allocation 가능성 (Small Object Optimization으로 일부 회피), 간접 호출 비용.
EBO (Empty Base Optimization)
빈 기반 클래스가 derived 클래스에 추가 패딩을 만들지 않도록 컴파일러가 허용하는 규칙을 활용한 이디엄. 정확히는 empty base subobject가 derived의 다른 subobject와 같은 주소를 차지할 수 있어서 결과적으로 크기 낭비가 사라진다.11
해결하는 문제: 정책 클래스, allocator 등 상태 없는 타입을 멤버로 두면 표준상 1바이트가 보장되어 낭비가 생긴다. 기반 클래스로 두면 그 1바이트가 사라진다.
struct EmptyDeleter {};
template<typename T, typename Deleter>
class UniquePtr : private Deleter // EBO: Deleter가 빈 타입이면 sizeof는 T*만
{
T* ptr;
};
C++20부터는 멤버에 [[no_unique_address]] 속성을 붙여 같은 최적화를 받을 수 있다. 그래서 EBO를 위해 일부러 상속을 쓰는 일은 점점 줄어드는 추세.
5. STL 사용 이디엄
Erase-remove idiom
컨테이너에서 조건에 맞는 원소를 제거할 때 std::remove_if와 erase를 함께 쓰는 패턴.12
해결하는 문제: std::remove_if는 이름과 달리 원소를 실제로 삭제하지 않는다. "지울 후보를 끝으로 몰아낸 뒤 새 logical end iterator를 반환"할 뿐. 진짜 삭제는 erase가 한다. 이 사실을 모르면 컨테이너 크기가 줄지 않고, 뒤쪽 구간에 논리적으로 제거된 원소가 valid but unspecified(또는 moved-from) 상태로 남는다.
// C++20 이전 - 표준 이디엄
v.erase(
std::remove_if(v.begin(), v.end(),
[](int x) { return x < 0; }),
v.end());
// C++20 이후 - 같은 동작의 단축형
std::erase_if(v, [](int x) { return x < 0; });
std::list 같은 노드 기반 컨테이너는 멤버 함수 remove_if를 따로 가지며 진짜로 삭제한다. 컨테이너별로 동작이 다르다는 점이 함정.
부록: idiom과 자주 혼동되는 언어 기능
이 둘은 named idiom이 아니라 C++11 언어 기능이다. idiom은 이 기능을 안전하게 활용하는 패턴들이다.
range-based for
C++11 syntax. iterator를 명시하지 않고 컬렉션을 순회. 이디엄이라기보단 권장 사용법.13
for (const auto& item : container) { /* ... */ }
for (auto& item : container) { /* 수정 가능 */ }
관련 이디엄: 인덱스가 필요하면 C++23의 std::views::enumerate를 사용할 수 있지만, 표준 라이브러리 구현 지원이 환경별로 다르다. 지원이 부족한 환경에서는 별도 카운터, range-v3, zip view + iota 조합 등을 사용한다.
move semantics
C++11 언어 기능. lvalue/rvalue 참조와 std::move로 리소스를 복사 대신 이전. 메커니즘 자체는 이디엄이 아니다.14
관련 이디엄들:
Rule of Five: 이동 가능 클래스 설계
Move-only types:
std::unique_ptr처럼 복사 금지하고 이동만 허용Sink parameter: 인자를 값으로 받고 내부에서 move하여 호출자가 복사할지 이동할지 선택하게 함
Perfect forwarding:
std::forward<T>로 인자의 value category 보존noexcept 이동: 표준 컨테이너가 이동을 채택하려면 이동 연산이
noexcept여야 함
// Sink parameter 이디엄
class Builder
{
public:
void set_name(std::string n) // 값으로 받기
{
name = std::move(n); // 내부에서 이동
}
private:
std::string name;
};
정리
분류 | 이디엄 |
|---|---|
리소스 관리 | RAII, Rule of Zero/Three/Five, Smart pointer ownership, copy-and-swap |
캡슐화 | Pimpl, NVI |
타입 표현 | std::optional, std::variant + std::visit |
컴파일 타임 | CRTP, Type erasure, EBO |
STL | Erase-remove |
(언어 기능) | range-based for, move semantics |
복잡한 도구를 쓴다고 사람이 깊어지는 것은 아니다.
때로는 단지 복잡한 도구를 합리화하는 데 익숙해질 뿐이다.
개인 메모: C++ 커뮤니티는 버전이 지나치게 많고, 표준도 복잡하고, 생태계도 파편화되어 있는데도 특유의 우월주의가 강하다.
저수준 제어를 한다는 자부심과, 현대적 안전성이나 생산성에 대한 경멸이 이상하게 섞여 있다.솔직히 별로 가까워지고 싶은 문화는 아니다.
그렇게까지 저수준 제어가 인생의 목적이라면 차라리 어셈블리를 쓰는 편이 더 일관적이지 않나 싶다.
참고문헌
[1] RAII.
Stroustrup, Bjarne. The C++ Programming Language. RAII 용어의 원전이며 Bjarne Stroustrup's C++ FAQ에 인용됨.
cppreference: https://en.cppreference.com/cpp/language/raii
Wikipedia: https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
[2] Rule of Zero / Three / Five.
Cline, Marshall (1991). Rule of Three 최초 제안.
cppreference: https://en.cppreference.com/cpp/language/rule_of_three
C++ Core Guidelines C.20 (Rule of Zero): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c20-if-you-can-avoid-defining-default-operations-do
C++ Core Guidelines C.21 (Rule of Five): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c21-if-you-define-or-delete-any-copy-move-or-destructor-function-define-or-delete-them-all
ACCU 역사 정리 (Stroustrup, Sutter/Alexandrescu, Walter Brown N3839까지 추적): https://accu.org/journals/overload/22/120/alday_1896/
Fluent C++ Rule of Zero (Scott Meyers의 "Rule of Five Defaults" 반론 포함): https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/
[3] Smart pointer ownership.
C++ Core Guidelines R.20–R.34 (Resource Management 섹션): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-resource
C++ Core Guidelines F.7 (non-owning은 raw pointer/reference로 받으라는 규칙): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f7-for-general-use-take-t-or-t-arguments-rather-than-smart-pointers
cppreference
std::unique_ptr: https://en.cppreference.com/cpp/memory/unique_ptrcppreference
std::shared_ptr: https://en.cppreference.com/cpp/memory/shared_ptrcppreference
std::weak_ptr: https://en.cppreference.com/cpp/memory/weak_ptr
[4] copy-and-swap.
Sutter, Herb (1999). Guru of the Week #59. 표현 방식으로 보아 그 이전부터 알려진 패턴: http://www.gotw.ca/gotw/059.htm
Stack Overflow canonical Q&A: https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom
Mropert (2019). copy-and-swap 20년 후 재평가 — strong exception guarantee 비용과 move 의미론과의 충돌 정리: https://mropert.github.io/2019/01/07/copy_swap_20_years/
[5] Pimpl (Pointer to Implementation).
Sutter, Herb. GotW #24 (원본): http://www.gotw.ca/gotw/024.htm
Sutter, Herb. GotW #100 (C++11 갱신): https://herbsutter.com/gotw/_100/
Sutter, Herb. GotW #101 (라이브러리 래퍼): https://herbsutter.com/gotw/_101/
C++ Stories — d-pointer, compiler firewall, Cheshire Cat, Opaque pointer 동의어 정리: https://www.cppstories.com/2018/01/pimpl/
[6] NVI (Non-Virtual Interface).
Sutter, Herb (2001). "Virtuality". C/C++ Users Journal. NVI를 명시적으로 권장한 가장 유명한 글: http://www.gotw.ca/publications/mill18.htm
C++ Core Guidelines C.133: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c133-avoid-protected-data
[7] std::optional.
cppreference: https://en.cppreference.com/cpp/utility/optional
C++ Core Guidelines F.60 (pointer 대신 optional 권장 맥락): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f60-prefer-t-over-t-when-no-argument-is-a-valid-option
cppreference
std::expected(C++23): https://en.cppreference.com/cpp/utility/expected
[8] std::variant + std::visit.
cppreference
std::variant: https://en.cppreference.com/cpp/utility/variantcppreference
std::visit: https://en.cppreference.com/cpp/utility/variant/visitC++ Core Guidelines C.181 (avoid naked union — variant를 union 대신 권장): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ru-naked
[9] CRTP (Curiously Recurring Template Pattern).
Coplien, James O. (1995). "Curiously Recurring Template Patterns". C++ Report, February 1995. 초기 C++ 템플릿 코드에서 이 패턴을 관찰하고 명명. PDF: http://sites.google.com/a/gertrudandcope.com/info/Publications/InheritedTemplate.pdf
Wikipedia (F-bound polymorphism의 일종으로 분류, Windows ATL/WTL에서 광범위하게 사용): https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
Fluent C++ 시리즈 (실용 해설): https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/
[10] Type erasure.
Parent, Sean (2013). "Inheritance Is The Base Class of Evil". GoingNative 2013. Value semantics를 사용한 비침습적 런타임 다형성 객체와 multiple-undo를 20분 안에 구현하는 시연.
Alexandrescu, Andrei. Modern C++ Design. "Type Erasure" 챕터.
Iglberger, Klaus. Breaking Dependencies: Type Erasure - A Design Analysis (CppCon talks).
[11] EBO (Empty Base Optimization).
cppreference: https://en.cppreference.com/cpp/language/ebo
C++20
[[no_unique_address]](멤버에 대한 현대적 대안): https://en.cppreference.com/cpp/language/attributes/no_unique_addressBoost
compressed_pair(EBO의 실용 응용 예시): https://www.boost.org/doc/libs/release/libs/utility/utility.htm
[12] Erase-remove idiom.
cppreference
std::remove(왜 그냥 지우지 않는가 설명 포함): https://en.cppreference.com/cpp/algorithm/removecppreference
std::erase/erase_if(C++20): https://en.cppreference.com/cpp/container/vector/erase2Meyers, Scott. Effective STL, Item 32: "Follow remove-like algorithms by erase if you really want to remove something".
[13] range-based for.
cppreference: https://en.cppreference.com/cpp/language/range-for
C++ Core Guidelines ES.71: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es71-prefer-a-range-for-statement-to-a-for-statement-when-there-is-a-choice
[14] move semantics.
cppreference: https://en.cppreference.com/cpp/language/move_constructor
C++ Core Guidelines F.18 (sink parameter): https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f18-for-will-not-be-retained-parameters-pass-by-x-and-stdmove-the-parameter
C++ Core Guidelines F.45 (do not return
T&&).Meyers, Scott. Effective Modern C++, Items 23–30 (rvalue references, move, forward).
각주
- [1] RAII. - Stroustrup, Bjarne. The C++ Programming Language. RAII 용어의 원전이며 Bjarne Stroustrup's C++ FAQ에 인용됨. - cppreference: <https://en.cppreference.com/cpp/language/raii> - Wikipedia: <https://en.wikipedia.org/wiki/Resourceacquisitionis_initialization> ↩
- [2] Rule of Zero / Three / Five. - Cline, Marshall (1991). Rule of Three 최초 제안. - cppreference: <https://en.cppreference.com/cpp/language/ruleofthree> - C++ Core Guidelines C.20 (Rule of Zero): <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c20-if-you-can-avoid-defining-default-operations-do> - C++ Core Guidelines C.21 (Rule of Five): <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c21-if-you-define-or-delete-any-copy-move-or-destructor-function-define-or-delete-them-all> - ACCU 역사 정리 (Stroustrup, Sutter/Alexandrescu, Walter Brown N3839까지 추적): <https://accu.org/journals/overload/22/120/alday_1896/> - Fluent C++ Rule of Zero (Scott Meyers의 "Rule of Five Defaults" 반론 포함): <https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/> ↩
- [3] Smart pointer ownership.
- C++ Core Guidelines R.20–R.34 (Resource Management 섹션): <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-resource>
- C++ Core Guidelines F.7 (non-owning은 raw pointer/reference로 받으라는 규칙): <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f7-for-general-use-take-t-or-t-arguments-rather-than-smart-pointers>
- cppreference
std::unique_ptr: <https://en.cppreference.com/cpp/memory/unique_ptr> - cppreferencestd::shared_ptr: <https://en.cppreference.com/cpp/memory/shared_ptr> - cppreferencestd::weak_ptr: <https://en.cppreference.com/cpp/memory/weak_ptr> ↩ - [4] copy-and-swap. - Sutter, Herb (1999). Guru of the Week #59. 표현 방식으로 보아 그 이전부터 알려진 패턴: <http://www.gotw.ca/gotw/059.htm> - Stack Overflow canonical Q&A: <https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom> - Mropert (2019). copy-and-swap 20년 후 재평가 — strong exception guarantee 비용과 move 의미론과의 충돌 정리: <https://mropert.github.io/2019/01/07/copyswap20_years/> ↩
- [5] Pimpl (Pointer to Implementation). - Sutter, Herb. GotW #24 (원본): <http://www.gotw.ca/gotw/024.htm> - Sutter, Herb. GotW #100 (C++11 갱신): <https://herbsutter.com/gotw/_100/> - Sutter, Herb. GotW #101 (라이브러리 래퍼): <https://herbsutter.com/gotw/_101/> - C++ Stories — d-pointer, compiler firewall, Cheshire Cat, Opaque pointer 동의어 정리: <https://www.cppstories.com/2018/01/pimpl/> ↩
- [6] NVI (Non-Virtual Interface). - Sutter, Herb (2001). "Virtuality". C/C++ Users Journal. NVI를 명시적으로 권장한 가장 유명한 글: <http://www.gotw.ca/publications/mill18.htm> - C++ Core Guidelines C.133: <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c133-avoid-protected-data> ↩
- [7] std::optional.
- cppreference: <https://en.cppreference.com/cpp/utility/optional>
- C++ Core Guidelines F.60 (pointer 대신 optional 권장 맥락): <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f60-prefer-t-over-t-when-no-argument-is-a-valid-option>
- cppreference
std::expected(C++23): <https://en.cppreference.com/cpp/utility/expected> ↩ - [8] std::variant + std::visit.
- cppreference
std::variant: <https://en.cppreference.com/cpp/utility/variant> - cppreferencestd::visit: <https://en.cppreference.com/cpp/utility/variant/visit> - C++ Core Guidelines C.181 (avoid naked union — variant를 union 대신 권장): <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ru-naked> ↩ - [9] CRTP (Curiously Recurring Template Pattern). - Coplien, James O. (1995). "Curiously Recurring Template Patterns". C++ Report, February 1995. 초기 C++ 템플릿 코드에서 이 패턴을 관찰하고 명명. PDF: <http://sites.google.com/a/gertrudandcope.com/info/Publications/InheritedTemplate.pdf> - Wikipedia (F-bound polymorphism의 일종으로 분류, Windows ATL/WTL에서 광범위하게 사용): <https://en.wikipedia.org/wiki/Curiouslyrecurringtemplate_pattern> - Fluent C++ 시리즈 (실용 해설): <https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/> ↩
- [10] Type erasure. - Parent, Sean (2013). "Inheritance Is The Base Class of Evil". GoingNative 2013. Value semantics를 사용한 비침습적 런타임 다형성 객체와 multiple-undo를 20분 안에 구현하는 시연. - Microsoft Learn: <https://learn.microsoft.com/en-us/shows/goingnative-2013/inheritance-base-class-of-evil> - YouTube: <https://www.youtube.com/watch?v=2bLkxj6EVoM> - 코드 정리 gist: <https://gist.github.com/martinmoene/cd3286daa799acc55cc0> - Alexandrescu, Andrei. Modern C++ Design. "Type Erasure" 챕터. - Iglberger, Klaus. Breaking Dependencies: Type Erasure - A Design Analysis (CppCon talks). ↩
- [11] EBO (Empty Base Optimization).
- cppreference: <https://en.cppreference.com/cpp/language/ebo>
- C++20
[[no_unique_address]](멤버에 대한 현대적 대안): <https://en.cppreference.com/cpp/language/attributes/nouniqueaddress> - Boostcompressed_pair(EBO의 실용 응용 예시): <https://www.boost.org/doc/libs/release/libs/utility/utility.htm> ↩ - [12] Erase-remove idiom.
- cppreference
std::remove(왜 그냥 지우지 않는가 설명 포함): <https://en.cppreference.com/cpp/algorithm/remove> - cppreferencestd::erase/erase_if(C++20): <https://en.cppreference.com/cpp/container/vector/erase2> - Meyers, Scott. Effective STL, Item 32: "Follow remove-like algorithms by erase if you really want to remove something". ↩ - [13] range-based for. - cppreference: <https://en.cppreference.com/cpp/language/range-for> - C++ Core Guidelines ES.71: <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es71-prefer-a-range-for-statement-to-a-for-statement-when-there-is-a-choice> ↩
- [14] move semantics.
- cppreference: <https://en.cppreference.com/cpp/language/move_constructor>
- C++ Core Guidelines F.18 (sink parameter): <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f18-for-will-not-be-retained-parameters-pass-by-x-and-stdmove-the-parameter>
- C++ Core Guidelines F.45 (do not return
T&&). - Meyers, Scott. Effective Modern C++, Items 23–30 (rvalue references, move, forward). ↩