계약 단언

계약 단언 (Contract assertions)

실행 중 특정 시점에 프로그램 상태가 어떤 속성을 성립하리라고 기대한다면, 그 기대를 코드에 명시할 수 있어요. 이걸 계약 단언(contract assertion)이라고 해요. 계약 단언 덕분에 "여기까지 왔으면 이 조건은 반드시 참이어야 한다"는 계약을 컴파일러와 런타임이 이해하도록 만들 수 있죠.

출처: cppreference

본문

계약 단언(contract assertion)은 프로그래머가 프로그램 상태의 속성 중 실행 중 특정 시점에 성립할 것으로 기대되는 것을 지정할 수 있게 해줘요.

설명 (Explanation)

계약 단언은 함수 계약 지정자(function contract specifier)와 contract_assert 문으로 도입돼요. 각 계약 단언은 bool 타입 표현식인 술어(predicate)를 가져요.

계약 단언의 평가 (Evaluating contract assertions)

계약 단언의 평가는 다음 평가 의미론(evaluation semantic) 중 하나를 사용해요:

평가 의미론 검사 의미론인가 종료 의미론인가
ignore
observe
enforce
quick-enforce

주어진 계약 단언 평가에 어떤 의미론이 쓰일지는 구현이 정의해요. 같은 계약 단언이라도 평가마다 의미론이 달라질 수 있고, 상수 평가 중의 평가도 포함돼요.

"ignore" 의미론을 쓰면 계약 단언의 평가는 아무 효과가 없어요.

검사 의미론을 쓰는 경우, 계약 단언의 평가 E는 술어의 값을 결정해요. 술어가 실제로 평가되는지는 불특정(unspecified)이에요. 다음 조건 중 하나라도 만족하면 계약 위반(contract violation)이 발생해요:

  • 술어를 평가했을 때 나올 값이 false인 경우
  • 술어의 평가가 예외로 빠져나가는 경우
  • 술어의 평가가 명백히 상수 평가되는(manifestly constant-evaluated) 문맥에서 수행되고, 술어가 핵심 상수 표현식(core constant expression)이 아닌 경우

E보다 먼저 일어나는 관측 가능한 체크포인트 CP가 있어서, A보다 먼저 일어나는 다른 어떤 연산 OPCP보다 먼저 일어나요.

int num = 0;
void f() pre((num++, false));

f(); // Increment of "num" might not occur, even if a checking semantic is used

계약 위반 처리 (Handling contract violations)

계약 위반이 명백히 상수 평가되는 문맥에서 발생하면:

  • 평가 의미론이 "observe"면 진단이 생성돼요.
  • 평가 의미론이 종료 의미론이면 프로그램은 ill-formed예요.

계약 위반이 명백히 상수 평가되지 않는 문맥에서 발생하면:

  • 평가 의미론이 "quick-enforce"면 프로그램이 계약 종료(contract-terminated)돼요.
  • 평가 의미론이 "enforce" 또는 "observe"면, 계약 위반 정보를 담은 const std::contracts::contract_violation 타입 객체 obj를 가리키는 lvalue로 계약 위반 처리기(contract-violation handler)가 호출돼요.
    • obj의 저장 공간은 불특정 방식으로 할당되지만, 전역 할당 함수는 호출되지 않아요.
    • obj의 수명은 계약 위반 처리기 호출 기간 동안 유지돼요.

계약 종료된 프로그램 (Contract-terminated programs)

프로그램이 계약 종료되면 (문맥에 따라) 다음 중 무엇이 일어날지는 구현이 정의해요:

  • std::terminate가 호출되거나,
  • std::abort가 호출되거나,
  • 실행이 종료됩니다 (추가 실행 단계가 발생하지 않음).

계약 위반 처리기 (Contract-violation handler)

프로그램의 계약 위반 처리기는 ::handle_contract_violation이라는 이름의 함수예요:

void handle_contract_violation( const std::contracts::contract_violation& ); // (since C++26) (optionally noexcept)

기본 계약 위반 처리기(default contract-violation handler)라고 하는 처리기의 정의는 (표준 라이브러리 헤더 대신) 구현이 제공해요.

계약 위반 처리기를 교체할 수 있는지는 구현이 정의해요. 교체할 수 없다면, 처리기의 교체 함수 선언은 진단이 요구되지 않는 ill-formed예요.

계약 위반 처리기가 정상적으로 반환하면:

  • 평가 의미론이 "observe"면 계약 단언 평가 지점 이후로 제어 흐름이 정상적으로 계속돼요.
  • 평가 의미론이 "enforce"면 프로그램이 계약 종료돼요.

계약 위반 처리기가 정상 반환한 뒤에 일어나는 관측 가능한 체크포인트 CP가 있어서, 처리기가 반환한 뒤에 일어나는 다른 어떤 연산 OPCP 뒤에 일어나요.

단언에서 발생한 예외 처리 (Handling exceptions from assertions)

계약 위반이 술어 평가가 예외로 빠져나가서 발생했고 평가 의미론이 "observe" 또는 "enforce"라면, 계약 위반 처리기는 그 예외에 대한 활성 암시적 처리기(implicit handler) 안에서 호출돼요.

계약 위반 처리기가 정상 반환하면:

  • 평가 의미론이 "observe"면 암시적 처리기는 더 이상 활성으로 간주되지 않아요.
  • 평가 의미론이 "enforce"면 계약 종료가 발생할 때 암시적 처리기는 활성 상태로 유지돼요.

현재 예외는 계약 위반 처리기 안에서 std::current_exception()으로 검사하거나 다시 던질 수 있어요.

순차 평가 (Evaluate in sequence)

계약 단언 목록 R을 순서대로 평가하려면:

  1. 다음 조건을 모두 만족하는 계약 단언 목록 S를 구성해요:

    • R의 모든 원소가 S에 들어 있어요.
    • R의 각 원소는 S 안에서 구현이 정의한 횟수만큼 반복될 수 있어요.
    • 계약 단언 AR에서 계약 단언 B보다 앞서면, S에서 A의 첫 번째 등장이 B의 첫 번째 등장보다 앞서요.
  2. S의 각 원소를, S에서 AB보다 앞서면 A의 평가가 B의 평가보다 먼저 순서 지정되도록(sequenced before) 평가해요.

void f(int i)
{
    contract_assert(i > 0);  // #1
    contract_assert(i < 10); // #2
    // valid sequence of evaluations:   #1 #2       (no repeat)
    // valid sequence of evaluations:   #1 #1 #2 #2 (repeat in sequence)
    // valid sequence of evaluations:   #1 #2 #1 #2 (repeat alternatively)
    // valid sequence of evaluations:   #1 #2 #2 #1 (second occurences can switch order)
    // invalid sequence of evaluations: #2 #1       (first occurences cannot switch)
}

참고 (Notes)

가능한 평가 의미론의 범위와 유연성은 구현에 따라 달라지고, 네 가지 의미론을 모두 허용할 필요는 없어요.

서로 다른 번역 단위에서 같은 계약 단언에 대해 서로 다른 평가 의미론을 선택하면, 계약 단언이 상수 표현식이 만드는 값을 바꾸는 부수 효과를 가질 때 one-definition rule 위반이 생길 수 있어요:

constexpr int f(int i)
{
    contract_assert((++const_cast<int&>(i), true));
    return i;
}

inline void g()
{
    int a[f(1)]; // size dependent on the evaluation semantic of contract_assert above
}

술어를 평가했을 때 나올 값이 true라면 계약 위반이 발생하지 않고, 계약 단언 평가 지점 이후로 제어 흐름이 정상적으로 계속돼요.

술어의 평가가 비지역 점프(non-local jump)나 프로그램 종료로 끝나면 계약 위반도 발생하지 않아요.

C++ 표준은 기본 계약 위반 처리기가 인자에서 가장 관련성 높은 내용을 적절히 서식화한 진단 출력을 (관찰된 계약 단언의 반복 위반에는 속도 제한을 걸어) 만들고, 그다음 정상 반환하도록 권장해요.

Feature-test 매크로 표준 기능
__cpp_contracts 202502L (C++26) Contracts
202606L (C++29) Contracts for virtual functions

키워드 (Keywords)

contract_assert, pre, post

더 알아보기 (Learn more)

  • contract_assert 문 (C++26) — 실행 중 내부 조건을 검증해요.
  • 함수 계약 지정자 (C++26) — 선행 조건(pre)과 후행 조건(post)을 지정해요.