본문으로 건너뛰기 C++26 Contracts 완벽 가이드 | 언어 레벨 계약 프로그래밍

C++26 Contracts 완벽 가이드 | 언어 레벨 계약 프로그래밍

C++26 Contracts 완벽 가이드 | 언어 레벨 계약 프로그래밍

이 글의 핵심

C++26 Contracts로 함수의 사전조건, 사후조건, 불변식을 언어 레벨에서 표현하세요. pre, post, contract_assert 문법부터 빌드 모드, 실전 패턴까지 다룹니다.

들어가며

C++26의 Contracts는 함수의 사전조건(precondition), 사후조건(postcondition), 불변식(invariant)언어 레벨에서 표현하는 기능입니다. 기존에는 assert, 주석, 또는 수동 검증 코드로 처리하던 것을 표준 문법으로 명시하며, 컴파일러 플래그로 검증 수준을 제어할 수 있습니다. 이 글은 Contracts의 기본 문법 (pre, post, contract_assert), 빌드 모드, 실전 패턴, 기존 방식과의 비교를 코드 예제와 함께 설명합니다. 학습 전제 조건


Contracts란?

Design by Contract

Contracts는 Bertrand Meyer의 “Design by Contract” 개념을 C++에 도입한 것입니다:

함수 = 계약
- 사전조건 (Precondition): 호출자가 보장해야 할 것
- 사후조건 (Postcondition): 함수가 보장할 것
- 불변식 (Invariant): 항상 참이어야 할 것

기존 방식의 한계

assert 사용:

#include <cassert>
int divide(int a, int b) {
    assert(b != 0);  // 디버그 빌드에서만 동작
    return a / b;
}
  • Release 빌드에서는 무시됨
  • 사전조건인지 불변식인지 불명확
  • 컴파일러 최적화에 힌트 제공 안 됨 수동 검증:
int divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}
  • 성능 오버헤드 (항상 검사)
  • 예외 처리 복잡도 증가
  • 계약 의도가 명확하지 않음 C++26 Contracts:
int divide(int a, int b)
    pre(b != 0)  // 사전조건 명시
{
    return a / b;
}
  • 의도 명확
  • 빌드 모드로 검증 수준 제어
  • 컴파일러 최적화 가능

기본 문법

사전조건: pre

// 함수 선언 뒤에 사전조건 명시
int sqrt_int(int x)
    pre(x >= 0)  // x는 음수가 아니어야 함
{
    // 구현
    return static_cast<int>(std::sqrt(x));
}
// 여러 조건
void process(int* ptr, int size)
    pre(ptr != nullptr)
    pre(size > 0)
{
    for (int i = 0; i < size; i++) {
        ptr[i] *= 2;
    }
}

사후조건: post

int factorial(int n)
    pre(n >= 0)
    post(r: r > 0)  // 반환값 r은 양수여야 함
{
    if (n == 0) return 1;
    return n * factorial(n - 1);
}
// 반환값 이름 지정
std::vector<int> sorted(std::vector<int> v)
    post(result: std::is_sorted(result.begin(), result.end()))
{
    std::sort(v.begin(), v.end());
    return v;
}

불변식: contract_assert

void binary_search(const std::vector<int>& arr, int target)
    pre(std::is_sorted(arr.begin(), arr.end()))
{
    int left = 0, right = arr.size() - 1;
    
    while (left <= right) {
        contract_assert(left >= 0 && right < arr.size());  // 불변식
        
        int mid = left + (right - left) / 2;
        
        if (arr[mid] == target) {
            return;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
}

빌드 모드

4가지 모드

1. ignore 모드

g++ -std=c++26 -fcontracts=ignore main.cpp
  • Contract 검사 완전히 무시
  • 성능 오버헤드 제로
  • 프로덕션 최적화 빌드에 적합 2. observe 모드
g++ -std=c++26 -fcontracts=observe main.cpp
  • Contract 위반 시 로그만 남기고 계속 실행
  • 프로그램 종료하지 않음
  • 프로덕션 모니터링에 적합 3. enforce 모드
g++ -std=c++26 -fcontracts=enforce main.cpp
  • Contract 위반 시 std::terminate() 호출
  • 프로그램 즉시 종료
  • 개발/테스트 환경에 적합 4. quick-enforce 모드
g++ -std=c++26 -fcontracts=quick-enforce main.cpp
  • 간단한 조건만 검사 (복잡한 조건 무시)
  • 성능과 안전성 균형
  • 프로덕션 빌드에서 최소 검증

모드별 동작 비교

int divide(int a, int b)
    pre(b != 0)
{
    return a / b;
}
int main() {
    int result = divide(10, 0);  // Contract 위반
    std::cout << result << '\n';
}
모드동작
ignore검사 안 함, 정의되지 않은 동작 발생
observe로그 출력 후 계속 실행 (UB 발생 가능)
enforcestd::terminate() 호출, 프로그램 종료
quick-enforce간단한 조건만 검사

사전조건 (Precondition)

기본 사용

#include <vector>
#include <stdexcept>
// 배열 인덱스 접근
int get_element(const std::vector<int>& vec, size_t index)
    pre(index < vec.size())
{
    return vec[index];
}
// 포인터 검증
void process_data(const int* data, size_t size)
    pre(data != nullptr)
    pre(size > 0)
{
    for (size_t i = 0; i < size; i++) {
        std::cout << data[i] << ' ';
    }
}
// 범위 검증
double calculate_percentage(int part, int total)
    pre(total > 0)
    pre(part >= 0 && part <= total)
{
    return (static_cast<double>(part) / total) * 100.0;
}

복잡한 조건

#include <algorithm>
// 정렬 여부 확인
int binary_search(const std::vector<int>& arr, int target)
    pre(std::is_sorted(arr.begin(), arr.end()))
{
    auto it = std::lower_bound(arr.begin(), arr.end(), target);
    return (it != arr.end() && *it == target) ? 
           std::distance(arr.begin(), it) : -1;
}
// 커스텀 검증 함수
bool is_valid_email(const std::string& email) {
    return email.find('@') != std::string::npos;
}
void send_email(const std::string& email, const std::string& message)
    pre(is_valid_email(email))
    pre(!message.empty())
{
    // 이메일 전송 로직
}

클래스 메서드

class BankAccount {
private:
    double balance;
    
public:
    BankAccount(double initial)
        pre(initial >= 0)
        : balance(initial) {}
    
    void deposit(double amount)
        pre(amount > 0)
        post(balance >= old(balance))  // old(): 함수 시작 시점의 값
    {
        balance += amount;
    }
    
    void withdraw(double amount)
        pre(amount > 0)
        pre(amount <= balance)
        post(balance == old(balance) - amount)
    {
        balance -= amount;
    }
    
    double get_balance() const
        post(r: r >= 0)  // 잔액은 항상 음수가 아님
    {
        return balance;
    }
};

사후조건 (Postcondition)

반환값 검증

// 반환값 이름 지정
int abs(int x)
    post(result: result >= 0)
{
    return (x < 0) ? -x : x;
}
// 여러 조건
std::vector<int> create_range(int start, int end)
    pre(start <= end)
    post(result: result.size() == static_cast<size_t>(end - start + 1))
    post(result: result.front() == start)
    post(result: result.back() == end)
{
    std::vector<int> v;
    for (int i = start; i <= end; i++) {
        v.push_back(i);
    }
    return v;
}

old() 함수 (이전 값 참조)

class Counter {
private:
    int count = 0;
    
public:
    void increment()
        post(count == old(count) + 1)  // 정확히 1 증가
    {
        count++;
    }
    
    void add(int n)
        pre(n >= 0)
        post(count == old(count) + n)
    {
        count += n;
    }
    
    void reset()
        post(count == 0)
    {
        count = 0;
    }
};

복잡한 사후조건

#include <algorithm>
// 정렬 보장
std::vector<int> sort_and_deduplicate(std::vector<int> v)
    post(result: std::is_sorted(result.begin(), result.end()))
    post(result: std::adjacent_find(result.begin(), result.end()) == result.end())  // 중복 없음
    post(result: result.size() <= v.size())
{
    std::sort(v.begin(), v.end());
    v.erase(std::unique(v.begin(), v.end()), v.end());
    return v;
}
// 불변 속성 유지
std::vector<int> filter_positive(const std::vector<int>& v)
    post(result: std::all_of(result.begin(), result.end(), 
                             [](int x) { return x > 0; }))
{
    std::vector<int> result;
    std::copy_if(v.begin(), v.end(), std::back_inserter(result),
                 [](int x) { return x > 0; });
    return result;
}

불변식 (Assertion)

contract_assert 사용

void process_array(int* arr, int size)
    pre(arr != nullptr)
    pre(size > 0)
{
    for (int i = 0; i < size; i++) {
        contract_assert(i >= 0 && i < size);  // 루프 불변식
        
        arr[i] *= 2;
        
        contract_assert(arr[i] % 2 == 0);  // 결과 검증
    }
}
// 복잡한 자료구조 불변식
class BinarySearchTree {
private:
    struct Node {
        int value;
        Node* left;
        Node* right;
    };
    
    Node* root;
    
    bool is_valid_bst(Node* node, int min_val, int max_val) const {
        if (!node) return true;
        if (node->value <= min_val || node->value >= max_val) return false;
        return is_valid_bst(node->left, min_val, node->value) &&
               is_valid_bst(node->right, node->value, max_val);
    }
    
public:
    void insert(int value) {
        // 삽입 로직
        
        contract_assert(is_valid_bst(root, INT_MIN, INT_MAX));  // BST 속성 유지
    }
};

실전 패턴

1. 배열/벡터 안전 접근

template<typename T>
class SafeVector {
private:
    std::vector<T> data;
    
public:
    void push_back(const T& value) {
        data.push_back(value);
    }
    
    T& at(size_t index)
        pre(index < data.size())
        post(result: &result >= data.data() && 
                     &result < data.data() + data.size())
    {
        return data[index];
    }
    
    const T& at(size_t index) const
        pre(index < data.size())
    {
        return data[index];
    }
    
    size_t size() const
        post(result: result == data.size())
    {
        return data.size();
    }
};

2. 리소스 관리

class FileHandle {
private:
    FILE* file = nullptr;
    
    bool is_open() const {
        return file != nullptr;
    }
    
public:
    void open(const char* filename)
        pre(filename != nullptr)
        pre(!is_open())
        post(is_open())
    {
        file = fopen(filename, "r");
        if (!file) {
            throw std::runtime_error("Failed to open file");
        }
    }
    
    void close()
        pre(is_open())
        post(!is_open())
    {
        if (file) {
            fclose(file);
            file = nullptr;
        }
    }
    
    size_t read(char* buffer, size_t size)
        pre(is_open())
        pre(buffer != nullptr)
        pre(size > 0)
        post(result: result <= size)
    {
        return fread(buffer, 1, size, file);
    }
    
    ~FileHandle() {
        if (is_open()) {
            close();
        }
    }
};

3. 수학 함수

#include <cmath>
#include <limits>
double safe_sqrt(double x)
    pre(x >= 0)
    post(result: result >= 0)
    post(result: std::abs(result * result - x) < 0.0001)  // 정확도 검증
{
    return std::sqrt(x);
}
double safe_log(double x)
    pre(x > 0)
    post(result: !std::isnan(result))
    post(result: !std::isinf(result))
{
    return std::log(x);
}
int safe_factorial(int n)
    pre(n >= 0)
    pre(n <= 12)  // int 오버플로우 방지
    post(result: result > 0)
{
    int result = 1;
    for (int i = 2; i <= n; i++) {
        contract_assert(result <= INT_MAX / i);  // 오버플로우 검사
        result *= i;
    }
    return result;
}

4. 문자열 처리

std::string substring(const std::string& str, size_t pos, size_t len)
    pre(pos < str.size())
    pre(len > 0)
    post(result: result.size() <= len)
    post(result: result.size() <= str.size() - pos)
{
    return str.substr(pos, len);
}
std::string to_uppercase(std::string str)
    post(result: result.size() == str.size())
    post(result: std::all_of(result.begin(), result.end(), 
                             [](char c) { return !std::islower(c); }))
{
    std::transform(str.begin(), str.end(), str.begin(), ::toupper);
    return str;
}

5. 컨테이너 불변식

template<typename T>
class Stack {
private:
    std::vector<T> data;
    
    bool is_valid() const {
        return data.capacity() >= data.size();
    }
    
public:
    void push(const T& value)
        post(size() == old(size()) + 1)
        post(top() == value)
    {
        data.push_back(value);
        contract_assert(is_valid());
    }
    
    T pop()
        pre(!empty())
        post(size() == old(size()) - 1)
    {
        T value = data.back();
        data.pop_back();
        contract_assert(is_valid());
        return value;
    }
    
    const T& top() const
        pre(!empty())
    {
        return data.back();
    }
    
    bool empty() const
        post(result: result == (size() == 0))
    {
        return data.empty();
    }
    
    size_t size() const
        post(result: result == data.size())
    {
        return data.size();
    }
};

성능 영향

벤치마크 (참고용)

간단한 조건 (x != 0):

int divide(int a, int b)
    pre(b != 0)
{
    return a / b;
}
  • ignore 모드: 0% 오버헤드
  • quick-enforce 모드: ~1-2% 오버헤드
  • enforce 모드: ~2-5% 오버헤드 복잡한 조건 (정렬 검증):
// 변수 선언 및 초기화
int binary_search(const std::vector<int>& arr, int target)
    pre(std::is_sorted(arr.begin(), arr.end()))
{
    // ...
}
  • ignore 모드: 0% 오버헤드
  • quick-enforce 모드: 검사 안 함 (복잡도 높음)
  • enforce 모드: O(N) 검사 → 큰 오버헤드

최적화 전략

1. 핫 패스는 ignore 모드

// 성능 크리티컬한 내부 함수
// 실행 예제
inline int fast_add(int a, int b)
    [[likely_ignore]]  // 힌트: ignore 모드 권장
{
    return a + b;
}

2. 공개 API는 enforce 모드

// 외부 입력 검증
void public_api(const std::string& input)
    [[likely_enforce]]  // 힌트: enforce 모드 권장
    pre(!input.empty())
    pre(input.size() <= 1000)
{
    // 처리
}

3. 조건 복잡도 최소화

// ❌ 비싼 조건
void process(const std::vector<int>& v)
    pre(std::is_sorted(v.begin(), v.end()))  // O(N)
    pre(std::all_of(v.begin(), v.end(), [](int x) { return x > 0; }))  // O(N)
{
    // ...
}
// ✅ 간단한 조건으로 분리
void process(const std::vector<int>& v)
    pre(!v.empty())  // O(1)
{
    contract_assert(std::is_sorted(v.begin(), v.end()));  // 디버그 빌드만
    // ...
}

기존 방식과 비교

assert vs Contracts

assert (C++03):

#include <cassert>
int divide(int a, int b) {
    assert(b != 0);  // NDEBUG 정의 시 무시
    return a / b;
}
  • Debug 빌드만 동작
  • Release 빌드에서 완전히 제거
  • 사전/사후조건 구분 없음 Contracts (C++26):
int divide(int a, int b)
    pre(b != 0)
{
    return a / b;
}
  • 빌드 모드로 세밀한 제어
  • 사전/사후조건 명시적 구분
  • 컴파일러 최적화 힌트

예외 vs Contracts

예외 처리:

int divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}
// 호출부
try {
    int result = divide(10, 0);
} catch (const std::exception& e) {
    std::cerr << e.what() << '\n';
}
  • 런타임 검사 (항상 실행)
  • 예외 처리 오버헤드
  • 복구 가능 Contracts:
int divide(int a, int b)
    pre(b != 0)
{
    return a / b;
}
// 호출부
int result = divide(10, 0);  // Contract 위반 → terminate
  • 빌드 모드에 따라 검사
  • 성능 오버헤드 최소
  • 복구 불가 (프로그램 종료) 언제 무엇을 사용? | 상황 | 권장 | |------|------| | 프로그래머 오류 (버그) | Contracts | | 외부 입력 검증 | 예외 | | 복구 가능한 에러 | 예외 | | 성능 크리티컬 | Contracts (ignore 모드) |

고급 패턴

1. 클래스 불변식

class CircularBuffer {
private:
    std::vector<int> buffer;
    size_t capacity;
    size_t head = 0;
    size_t tail = 0;
    size_t count = 0;
    
    bool is_valid() const {
        return count <= capacity &&
               head < capacity &&
               tail < capacity &&
               buffer.size() == capacity;
    }
    
public:
    CircularBuffer(size_t cap)
        pre(cap > 0)
        post(is_valid())
        : capacity(cap), buffer(cap) {}
    
    void push(int value)
        pre(!full())
        post(size() == old(size()) + 1)
        post(is_valid())
    {
        buffer[tail] = value;
        tail = (tail + 1) % capacity;
        count++;
        
        contract_assert(is_valid());
    }
    
    int pop()
        pre(!empty())
        post(size() == old(size()) - 1)
        post(is_valid())
    {
        int value = buffer[head];
        head = (head + 1) % capacity;
        count--;
        
        contract_assert(is_valid());
        return value;
    }
    
    bool empty() const
        post(result: result == (count == 0))
    {
        return count == 0;
    }
    
    bool full() const
        post(result: result == (count == capacity))
    {
        return count == capacity;
    }
    
    size_t size() const
        post(result: result <= capacity)
    {
        return count;
    }
};

2. 알고리즘 불변식

// 퀵소트 불변식
void quicksort(std::vector<int>& arr, int low, int high)
    pre(low >= 0)
    pre(high < static_cast<int>(arr.size()))
    post(std::is_sorted(arr.begin() + low, arr.begin() + high + 1))
{
    if (low >= high) return;
    
    int pivot = arr[high];
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        contract_assert(j >= low && j < high);  // 루프 불변식
        
        if (arr[j] < pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    
    std::swap(arr[i + 1], arr[high]);
    int pi = i + 1;
    
    contract_assert(pi >= low && pi <= high);
    
    quicksort(arr, low, pi - 1);
    quicksort(arr, pi + 1, high);
}
// 이진 탐색 불변식
int binary_search(const std::vector<int>& arr, int target)
    pre(std::is_sorted(arr.begin(), arr.end()))
    post(result: result == -1 || 
                 (result >= 0 && result < static_cast<int>(arr.size()) && 
                  arr[result] == target))
{
    int left = 0, right = arr.size() - 1;
    
    while (left <= right) {
        contract_assert(left >= 0 && right < static_cast<int>(arr.size()));
        contract_assert(left <= right + 1);
        
        int mid = left + (right - left) / 2;
        
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return -1;
}

3. 동시성 불변식

#include <mutex>
#include <thread>
class ThreadSafeCounter {
private:
    mutable std::mutex mtx;
    int count = 0;
    
    bool is_locked() const {
        // 뮤텍스가 현재 스레드에 의해 잠겨있는지 확인
        // (실제 구현은 더 복잡)
        return true;  // 간소화
    }
    
public:
    void increment()
        post(count == old(count) + 1)
    {
        std::lock_guard<std::mutex> lock(mtx);
        contract_assert(is_locked());
        
        count++;
    }
    
    int get() const {
        std::lock_guard<std::mutex> lock(mtx);
        contract_assert(is_locked());
        
        return count;
    }
};

4. 스마트 포인터 래퍼

template<typename T>
class NonNullPtr {
private:
    T* ptr;
    
public:
    explicit NonNullPtr(T* p)
        pre(p != nullptr)
        post(ptr != nullptr)
        : ptr(p) {}
    
    T& operator*()
        pre(ptr != nullptr)
    {
        return *ptr;
    }
    
    T* operator->()
        pre(ptr != nullptr)
        post(result: result != nullptr)
    {
        return ptr;
    }
    
    T* get()
        pre(ptr != nullptr)
        post(result: result != nullptr)
    {
        return ptr;
    }
};

실무 적용 가이드

단계별 도입

1단계: 공개 API에 사전조건 추가

// 라이브러리 공개 함수
void process_data(const char* data, size_t size)
    pre(data != nullptr)
    pre(size > 0)
{
    // 구현
}

2단계: 중요 함수에 사후조건 추가

// 실행 예제
std::vector<int> merge_sorted(const std::vector<int>& a, 
                               const std::vector<int>& b)
    pre(std::is_sorted(a.begin(), a.end()))
    pre(std::is_sorted(b.begin(), b.end()))
    post(result: std::is_sorted(result.begin(), result.end()))
    post(result: result.size() == a.size() + b.size())
{
    // 구현
}

3단계: 복잡한 로직에 불변식 추가

void complex_algorithm() {
    // 초기 상태 검증
    contract_assert(is_valid_state());
    
    // 작업 수행
    step1();
    contract_assert(is_valid_state());
    
    step2();
    contract_assert(is_valid_state());
    
    step3();
    contract_assert(is_valid_state());
}

빌드 설정 예제

CMakeLists.txt:

# Debug 빌드: enforce 모드
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fcontracts=enforce")
# Release 빌드: ignore 모드
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fcontracts=ignore")
# RelWithDebInfo: observe 모드
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fcontracts=observe")

트러블슈팅

문제 1: Contract 위반 시 프로그램 종료

증상:

terminate called after throwing an instance of 'std::contract_violation'

해결:

// 1. 호출부에서 조건 확인
if (b != 0) {
    result = divide(a, b);
}
// 2. observe 모드로 변경 (개발 중)
// -fcontracts=observe
// 3. 조건 완화
int divide(int a, int b)
    pre(b != 0 || (std::cerr << "Warning: division by zero\n", false))
{
    return b != 0 ? a / b : 0;
}

문제 2: 복잡한 조건으로 빌드 느림

증상:

void process(const std::vector<int>& v)
    pre(std::all_of(v.begin(), v.end(), is_complex_condition))  // 매우 느림
{
    // ...
}

해결:

// 1. 조건 간소화
void process(const std::vector<int>& v)
    pre(!v.empty())  // 간단한 조건만
{
    contract_assert(std::all_of(v.begin(), v.end(), is_complex_condition));
    // ...
}
// 2. 디버그 전용 검증
#ifdef DEBUG_CONTRACTS
    pre(std::all_of(v.begin(), v.end(), is_complex_condition))
#endif

문제 3: old() 값이 예상과 다름

증상:

void increment(int& x)
    post(x == old(x) + 1)
{
    x++;
    x++;  // ❌ 버그: 2 증가
}

해결

  • Contract 위반이 버그를 찾아줌
  • 구현 수정 필요

마무리

C++26 Contracts는 Design by Contract를 언어 레벨로 가져온 혁신적 기능입니다: 핵심 장점

  • 명시적 계약: 함수의 요구사항과 보장을 코드로 표현

  • 빌드 모드 제어: ignore/observe/enforce로 유연한 검증

  • 제로 오버헤드 가능: ignore 모드에서 성능 영향 없음

  • 버그 조기 발견: 개발 단계에서 계약 위반 검출 주요 사용 패턴

  • pre: 함수 호출 전 조건 (호출자 책임)

  • post: 함수 반환 후 조건 (구현자 책임)

  • contract_assert: 루프 불변식, 중간 상태 검증 도입 전략:

  1. 공개 API의 사전조건부터 시작
  2. 중요 함수에 사후조건 추가
  3. 복잡한 알고리즘에 불변식 추가
  4. 빌드 모드 최적화 (핫 패스는 ignore) 다음 학습

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++26 Contracts로 함수의 사전조건, 사후조건, 불변식을 언어 레벨에서 표현하세요. pre, post, contract_assert 문법부터 빌드 모드, 실전 패턴까지 다룹니다. Start now. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++26, Contracts, Precondition, Postcondition, Assertion, Design by Contract, Defensive Programming 등으로 검색하시면 이 글이 도움이 됩니다.