핵심 정리
cpp
class Buffer {
std::unique_ptr<int[]> data_;
size_t size_;
public:
// noexcept — 이동 연산은 실패하지 않음을 보장
Buffer(Buffer&& other) noexcept
: data_(std::move(other.data_)), size_(other.size_) {
other.size_ = 0;
}
Buffer& operator=(Buffer&& other) noexcept {
data_ = std::move(other.data_);
size_ = other.size_;
other.size_ = 0;
return *this;
}
};문법
예외 안전성 보장 수준 — 3단계
예외 안전성은 예외 발생 시 객체와 자원의 상태를 어떻게 보존하는지의 계약입니다.
cpp
class Account {
double balance_;
public:
// nothrow guarantee — 예외 절대 없음
double balance() const noexcept { return balance_; }
// basic guarantee — 예외 발생 시 유효한 상태 유지 (값은 불확실)
void deposit(double amount) {
if (amount < 0) throw std::invalid_argument("음수 입금 불가");
balance_ += amount; // 성공하면 정상, 예외 시 balance_ 그대로
}
// strong guarantee — 예외 발생 시 원래 상태로 완전 복원
void transfer(Account& to, double amount) {
if (balance_ < amount) throw std::runtime_error("잔액 부족");
balance_ -= amount; // 여기서 예외 없음
to.balance_ += amount; // 여기서 예외 없음
// 두 연산 모두 nothrow → strong guarantee 달성
}
};noexcept와 vector 재할당 — 이동 성능에 직접 영향
vector는 재할당 시 원소를 이동할지 복사할지 결정합니다. 이동 생성자가 noexcept이면 이동을 더 쉽게 선택할 수 있고, 그렇지 않으면 strong guarantee를 지키기 위해 복사 쪽으로 기울 수 있습니다. 다만 실제 선택은 타입의 복사 가능 여부와 구현 세부에도 영향을 받습니다.
cpp
struct WithNoexcept {
WithNoexcept(WithNoexcept&&) noexcept { } // ✅ 이동 생성자 noexcept
};
struct WithoutNoexcept {
WithoutNoexcept(WithoutNoexcept&&) { } // noexcept 없음
};
std::vector<WithNoexcept> v1; // 재할당 시 이동 — O(n) 이동
std::vector<WithoutNoexcept> v2; // 재할당 시 복사 — O(n) 복사 (느림)
// 확인: std::is_nothrow_move_constructible
static_assert(std::is_nothrow_move_constructible_v<WithNoexcept>);noexcept 잘못 사용 — std::terminate 호출
noexcept 함수 내부에서 예외가 발생하면 catch로 잡지 않고 즉시 std::terminate가 호출됩니다. 프로그램이 즉시 종료됩니다.
cpp
void risky() noexcept {
throw std::runtime_error("실패"); // ❌ noexcept 위반
// std::terminate() 호출 → 프로그램 종료
}
// ✅ 예외 가능성이 있으면 noexcept 붙이지 않기
void risky() { // noexcept 없음 — 예외 전파 허용
throw std::runtime_error("실패"); // ✅ 호출자가 catch 가능
}
// ✅ 정말 noexcept여야 하면 내부에서 예외를 잡아야 함
void safe() noexcept {
try {
mightThrow();
} catch (...) {
// 처리 또는 로그 — 예외가 밖으로 나가지 않도록
}
}체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 예외 절대 없음 보장 | noexcept 지정 |
| 이동 연산 (vector 성능 최적화) | 이동 ctor/대입에 noexcept |
| 예외 발생 시 원래 상태로 복원 | strong guarantee (copy-and-swap) |
| RAII 기반 자원 관리 | basic guarantee 자동 달성 |
| 소멸자 | 기본적으로 noexcept 취급되도록 설계 |
주의할 점
noexcept는 "예외를 밖으로 내보내지 않는다"는 강한 계약입니다. 실제로 예외가 전파되면 std::terminate로 프로그램이 즉시 종료됩니다.
cpp
// ❌ noexcept 함수에서 예외 발생 — 프로그램 종료
void bad() noexcept {
std::vector<int> v;
v.at(100); // std::out_of_range 예외 → std::terminate!
}
// ❌ 소멸자에서 예외 던지기 — 스택 언와인딩 중 terminate
struct Dangerous {
~Dangerous() {
throw std::runtime_error("dtor에서 예외"); // ❌ 절대 하면 안 됨
}
};
// ✅ 소멸자는 바깥으로 예외를 내보내지 않도록 설계
struct Safe {
~Safe() noexcept {
try { cleanup(); } catch (...) { /* 삼킴 */ }
}
};참고 링크
2 sources