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

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

이 글의 핵심

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

들어가며

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


실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. Contracts란?
  2. 기본 문법
  3. 빌드 모드
  4. 사전조건 (Precondition)
  5. 사후조건 (Postcondition)
  6. 불변식 (Assertion)
  7. 실전 패턴
  8. 성능 영향
  9. 기존 방식과 비교
  10. 마무리

Contracts란?

Design by Contract

Contracts는 Bertrand Meyer의 “Design by Contract” 개념을 C++에 도입한 것입니다: 다음은 간단한 code 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

기존 방식의 한계

assert 사용: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <cassert>
int divide(int a, int b) {
    assert(b != 0);  // 디버그 빌드에서만 동작
    return a / b;
}
  • Release 빌드에서는 무시됨
  • 사전조건인지 불변식인지 불명확
  • 컴파일러 최적화에 힌트 제공 안 됨 수동 검증: 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}
  • 성능 오버헤드 (항상 검사)
  • 예외 처리 복잡도 증가
  • 계약 의도가 명확하지 않음 C++26 Contracts: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int divide(int a, int b)
    pre(b != 0)  // 사전조건 명시
{
    return a / b;
}
  • 의도 명확
  • 빌드 모드로 검증 수준 제어
  • 컴파일러 최적화 가능

기본 문법

사전조건: pre

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 함수 선언 뒤에 사전조건 명시
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

다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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
  • 간단한 조건만 검사 (복잡한 조건 무시)
  • 성능과 안전성 균형
  • 프로덕션 빌드에서 최소 검증

모드별 동작 비교

아래 코드는 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)

기본 사용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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;
}

복잡한 조건

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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())
{
    // 이메일 전송 로직
}

클래스 메서드

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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)

반환값 검증

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 반환값 이름 지정
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() 함수 (이전 값 참조)

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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;
    }
};

복잡한 사후조건

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 사용

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 배열/벡터 안전 접근

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 리소스 관리

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 수학 함수

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 문자열 처리

다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 컨테이너 불변식

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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): 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

int divide(int a, int b)
    pre(b != 0)
{
    return a / b;
}
  • ignore 모드: 0% 오버헤드
  • quick-enforce 모드: ~1-2% 오버헤드
  • enforce 모드: ~2-5% 오버헤드 복잡한 조건 (정렬 검증): 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 변수 선언 및 초기화
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 모드 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

2. 공개 API는 enforce 모드 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

3. 조건 복잡도 최소화 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 비싼 조건
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): 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#include <cassert>
int divide(int a, int b) {
    assert(b != 0);  // NDEBUG 정의 시 무시
    return a / b;
}
  • Debug 빌드만 동작
  • Release 빌드에서 완전히 제거
  • 사전/사후조건 구분 없음 Contracts (C++26): 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int divide(int a, int b)
    pre(b != 0)
{
    return a / b;
}
  • 빌드 모드로 세밀한 제어
  • 사전/사후조건 명시적 구분
  • 컴파일러 최적화 힌트

예외 vs Contracts

예외 처리: 아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int divide(int a, int b)
    pre(b != 0)
{
    return a / b;
}
// 호출부
int result = divide(10, 0);  // Contract 위반 → terminate
  • 빌드 모드에 따라 검사
  • 성능 오버헤드 최소
  • 복구 불가 (프로그램 종료) 언제 무엇을 사용? | 상황 | 권장 | |------|------| | 프로그래머 오류 (버그) | Contracts | | 외부 입력 검증 | 예외 | | 복구 가능한 에러 | 예외 | | 성능 크리티컬 | Contracts (ignore 모드) |

고급 패턴

1. 클래스 불변식

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 알고리즘 불변식

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 퀵소트 불변식
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. 동시성 불변식

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 스마트 포인터 래퍼

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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에 사전조건 추가 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

2단계: 중요 함수에 사후조건 추가 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 실행 예제
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단계: 복잡한 로직에 불변식 추가 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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: 아래 코드는 cmake를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 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'

해결: 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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: 복잡한 조건으로 빌드 느림

증상: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

해결: 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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() 값이 예상과 다름

증상: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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) 다음 학습:
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3