← 기술 지식

기술 지식 / 프로그래밍 언어 / C++

C++ RAII 정리와 강한 예외 보장의 차이

자원 해제와 상태 원복을 구분하고 임시 상태 구성·무예외 교체·생성 중 실패·명시 commit·외부 내구성의 경계를 설명합니다.

언어·런타임 · 원리·구현·운영
기술 지식 트리

C++ 예외 안전성은 “예외가 났을 때 소멸자가 실행되는가”와 “호출자가 보던 업무 상태가 그대로인가”를 분리해서 말해야 정확해집니다. 이 노트는 RAII가 정리하는 자원, 임시 상태를 통한 강한 보장, 명시적 commit과 외부 내구성의 경계를 함께 다룹니다.

RAII 자원 정리와 업무 상태 원복

lock_guard가 보장하는 것은 잠금 소유권의 종료이지, 잠금 안에서 수행한 도메인 변경의 원복이 아닙니다. 강한 보장을 설계하려면 변경을 임시 객체에 쌓고 검증된 결과만 게시하거나, 외부 transaction·보상처럼 업무 상태를 되돌릴 별도 메커니즘을 둬야 하며 둘을 같은 RAII 설명으로 합치지 않습니다.

잠금 안에서 계정 A를 차감하고 B에 입금하려던 중 메모리 할당 예외가 났다고 합시다. lock_guard가 스코프를 벗어나 잠금을 풀어도 A의 차감은 그대로 남을 수 있습니다. RAII는 소유 객체의 수명에 자원 정리를 연결하는 관용구이지 모든 업무 변경의 자동 롤백이 아닙니다.

파일 핸들 닫기, 임시 메모리 해제, 잠금 해제와 관찰 가능한 데이터 상태 복원은 다른 계약입니다. 예외 안전성을 말할 때 무엇이 보장되는지 구분해야 합니다.

기본·강한·무예외 보장 구분

실패 지점을 입력 검증, 임시 구성, 교체, 정리로 나누어 각 지점에서 호출자가 볼 수 있는 상태와 자원 누수 여부를 따로 적으면 보장 수준을 과장하지 않게 됩니다. noexcept는 함수 밖으로 예외를 내보내지 않는 선언일 뿐, 내부 실패를 성공으로 바꾸거나 프로세스 종료를 막는 약속은 아닙니다.

보장 실패 뒤 약속 비용·조건
기본 보장 자원 누수 없이 유효한 불변식 유지 값은 바뀔 수 있음
강한 보장 관찰 가능한 상태가 변경 전과 같음 임시 상태·추가 메모리·커밋 설계
무예외 보장 예외가 밖으로 나오지 않음 실제 구현이 던지지 않아야 함

소멸자를 noexcept로 선언해도 내부 작업의 실패가 성공으로 바뀌는 것은 아닙니다. 예외가 빠져나오면 terminate될 수 있습니다. 다른 예외로 스택을 푸는 동안 소멸자에서 또 예외를 전파하는 구조는 특히 피해야 합니다.

새 상태 준비와 단일 경계 게시

기존 state version=7에서 새 state를 만든 동안 다른 writer가 version=8을 게시하면, swap 직전 expected version 검사가 없을 때 새 계산이 최신 변경을 덮을 수 있습니다. 이 경우 선택지는 재계산, writer 직렬화, 또는 version 조건부 게시이며, 무예외 swap 자체가 동시성 충돌 해결을 대신하지 않습니다.

기존 설정의 routes와 index가 서로 맞아야 한다면 routes만 먼저 바꾼 뒤 index를 만들지 않습니다. 임시 State에 둘 다 완성하고 검증한 다음, 해당 타입·allocator 조건에서 실제로 던지지 않는 교체로 게시합니다.

replacement = build_complete_state(input)   # 실패해도 current는 그대로
validate_invariant(replacement)
with lock protecting current:
    check expected version if derived from old current
    swap_without_throw(current, replacement)
# replacement가 이제 옛 상태를 소유하며 정리
준비 실패는 원본에 닿기 전에 끝냅니다기존 State 유지. 새 State 전체 구성. 불변식·기대 버전 확인. 무예외 단일 교체. 옛 State 정리현재 버전완성된 대안게시 허용옛 소유권 이전기존 State 유지새 State 전체 구성불변식·기대 버전 확인무예외 단일 교체옛 State 정리
준비 실패는 원본에 닿기 전에 끝냅니다 화살표는 상태 준비와 게시 순서입니다. 교체의 예외 안전성과 여러 스레드에 대한 동기화는 별도이므로 독자도 같은 잠금 또는 올바른 snapshot 게시를 따라야 합니다.
그림을 글로 읽기
  • 기존 State 유지
  • 새 State 전체 구성
  • 불변식·기대 버전 확인
  • 무예외 단일 교체
  • 옛 State 정리
  1. 기존 State 유지 → 불변식·기대 버전 확인: 현재 버전
  2. 새 State 전체 구성 → 불변식·기대 버전 확인: 완성된 대안
  3. 불변식·기대 버전 확인 → 무예외 단일 교체: 게시 허용
  4. 무예외 단일 교체 → 옛 State 정리: 옛 소유권 이전

routes와 index를 독립 객체로 둔 상태에서 첫 번째 swap 뒤 두 번째 swap이 예외를 던지면, 잠금 없는 독자는 한쪽만 새 상태인 조합을 볼 수 있습니다. 비교자·allocator·외부 핸들처럼 함께 바뀌는 조건까지 새 불변식에 넣어 하나의 State로 준비하고, 그 State를 한 번에 게시해야 합니다.

잠금 없이 여러 필드를 읽는 독자에게는 swap이 원자 snapshot을 주지 않으므로 같은 잠금이나 올바른 snapshot 게시 경계를 사용합니다. 새 상태를 옛 상태에서 계산했다면 다른 writer의 변경을 덮지 않도록 게시 전에 기대 버전을 재검사하거나 재계산·직렬화합니다.

생성 중 실패와 자원 소유권

생성자에서 raw 파일 핸들을 얻은 뒤 다른 멤버 초기화가 던지면 생성에 실패한 객체 자체의 소멸자를 기대할 수 없습니다. 이미 완전히 생성된 멤버는 정리되므로 획득한 자원을 즉시 RAII 멤버에 소유시키는 구조가 안전합니다. “소멸자에서 마지막에 닫는다”만으로 부분 생성 실패를 보호할 수 없습니다.

스택 풀기가 일어나는 예외·정상 반환과 abort·프로세스 강제 종료도 다릅니다. 모든 종료에서 소멸자가 실행된다고 가정하지 않습니다. 외부 파일의 일관성은 임시 파일·원자 게시·복구 절차 등 별도 내구 설계가 필요합니다.

명시적 commit과 중요 실패 전달

파일 저장의 성공 기준을 write 반환, flush, fsync 수준의 내구화, rename 게시처럼 나누어 호출자에게 어느 단계가 완료됐는지 반환해야 합니다. commit 오류 뒤 재시도 가능 여부가 불명확하면 이미 외부에 반영된 변경을 중복 실행할 수 있으므로 상태를 unknown으로 남기고 조회·대사 경로를 우선합니다.

파일 저장 결과를 호출자가 알아야 한다면 명시적인 commit·flush·close에서 오류를 반환하거나 적절히 전파합니다. 소멸자는 남은 자원의 안전한 정리를 담당하고 중요 저장의 성공을 암묵적으로 결정하지 않게 합니다.

write가 성공해도 사용자 공간 버퍼 flush, OS 버퍼 반영, 디스크 내구화, 이름 교체의 내구성은 각각 다른 단계로 남습니다. 따라서 API는 이 중 어느 단계까지 완료되면 성공이라고 볼지 약속해야 합니다. commit이 실패했을 때 객체가 재시도 가능한지 폐기만 가능한지를 상태로 표현하고, 외부에 이미 적용됐는지 모르는 실패는 같은 변경을 무조건 재시도하지 않습니다. 첫 시도가 실제로 반영된 뒤 두 번째 시도가 실행되면 중복 효과가 생길 수 있기 때문입니다.

원래 예외와 cleanup 오류가 함께 나면 원래 실패를 잃지 않게 보존하고 정리 오류를 별도로 보고합니다. 비밀 데이터·파일 원문을 진단 로그에 남기지 않습니다. 보상 작업이 유용하더라도 보상 실패와 중간 관찰이 가능한 경우 강한 예외 보장과 같다고 부르지 않습니다.

실패 지점별 주입 시험

할당·복사·검증·swap 직전·commit·close에 순서대로 실패를 주입하고, 예상 결과를 “current는 이전 불변식 유지, 임시 자원은 해제, 외부 파일은 정의된 게시 상태”로 고정합니다. 이 실습은 실제 저장장치 장애를 증명하지 않으므로, 테스트 통과를 모든 프로세스 강제 종료와 동일한 보장으로 표현하지 않습니다.

임시 상태의 첫 할당·중간 원소 복사·검증·커밋 전 단계에 실패를 주입하고 원본 값과 불변식·열린 핸들 수를 대조합니다. 게시 뒤 옛 상태 정리 비용이 민감한 스레드의 지연을 만들지 않는지도 봅니다.

파일 실험은 일시적인 테스트 디렉터리에서 저장 실패·close 실패·중단을 구분합니다. 이 노트는 보장 설계와 시험 절차이며 아직 C++ 예제를 컴파일하거나 실제 저장 장애를 주입한 결과는 아닙니다.

후속·관련 개념

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 3개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.