C++ noexcept 키워드 | "예외 없음 지정" 가이드
이 글의 핵심
C++ noexcept 키워드에 대한 실전 가이드입니다.
noexcept란?
noexcept 는 C++11에서 도입된 키워드로, 함수가 예외를 던지지 않음을 명시합니다. 예외를 던지면 std::terminate가 호출되어 프로그램이 종료됩니다.
void func() noexcept {
// 예외 던지면 std::terminate
}
void func2() noexcept(true) { // noexcept와 동일
// ...
}
void func3() noexcept(false) { // 예외 가능
// ...
}
왜 필요한가?:
- 최적화: 컴파일러가 스택 되감기 코드 생략 가능
- STL 최적화:
std::vector가 noexcept 이동 연산자 확인 - 명확성: 함수가 예외를 던지지 않음을 명시
- 안전성: 예외 안전 보장이 필요한 곳에서 사용
// ❌ noexcept 없음
class Widget {
public:
Widget(Widget&& other) {
// std::vector는 복사 사용 (안전)
}
};
// ✅ noexcept 추가
class Widget {
public:
Widget(Widget&& other) noexcept {
// std::vector는 이동 사용 (빠름)
}
};
noexcept의 동작:
void func() noexcept {
throw std::runtime_error("에러"); // std::terminate 호출
}
// 개념적 동작
void func() {
try {
// 함수 본문
throw std::runtime_error("에러");
} catch (...) {
std::terminate(); // 모든 예외를 terminate로
}
}
noexcept vs throw():
| 특징 | noexcept (C++11) | throw() (C++98, deprecated) |
|---|---|---|
| 예외 시 | std::terminate | std::unexpected → std::terminate |
| 최적화 | ✅ 가능 | ❌ 제한적 |
| 조건부 | ✅ 가능 | ❌ 불가 |
| 권장 | ✅ 사용 | ❌ 사용 안함 |
// ❌ throw(): deprecated
void func() throw() {
// ...
}
// ✅ noexcept: 권장
void func() noexcept {
// ...
}
조건부 noexcept
template<typename T>
void func(T value) noexcept(noexcept(T(value))) {
T copy(value);
}
실전 예시
예시 1: 이동 연산
class Buffer {
int* data;
size_t size;
public:
// ✅ noexcept 이동 생성자
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// ✅ noexcept 이동 대입
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
예시 2: swap
class Widget {
public:
void swap(Widget& other) noexcept {
std::swap(data, other.data);
}
private:
int data;
};
// std::swap 특수화
namespace std {
template<>
void swap(Widget& a, Widget& b) noexcept {
a.swap(b);
}
}
예시 3: 소멸자
// 소멸자는 기본적으로 noexcept
class MyClass {
public:
~MyClass() noexcept { // 명시적 (선택적)
// 예외 던지면 std::terminate
}
};
예시 4: 조건부 noexcept
template<typename T>
class Container {
public:
void push_back(T&& value)
noexcept(noexcept(data.push_back(std::move(value)))) {
data.push_back(std::move(value));
}
private:
std::vector<T> data;
};
noexcept 연산자
// noexcept(표현식): bool 반환
void func() noexcept {}
static_assert(noexcept(func())); // true
void func2() {}
static_assert(!noexcept(func2())); // false
자주 발생하는 문제
문제 1: 예외 던지기
// ❌ noexcept에서 예외
void func() noexcept {
throw std::runtime_error("에러"); // std::terminate
}
// ✅ try-catch
void func() noexcept {
try {
riskyOperation();
} catch (...) {
// 예외 처리
}
}
문제 2: std::vector 최적화
class Widget {
public:
// ❌ noexcept 없음
Widget(Widget&&) {
// vector 재할당 시 복사 사용
}
// ✅ noexcept 추가
Widget(Widget&&) noexcept {
// vector 재할당 시 이동 사용
}
};
문제 3: 조건부 noexcept
template<typename T>
class Wrapper {
public:
// T의 이동 생성자가 noexcept면 noexcept
Wrapper(Wrapper&& other)
noexcept(std::is_nothrow_move_constructible_v<T>)
: value(std::move(other.value)) {}
private:
T value;
};
문제 4: 소멸자
// 소멸자는 암시적 noexcept
class MyClass {
public:
~MyClass() {
// 예외 던지면 std::terminate
}
};
// 명시적 noexcept(false) (권장 안함)
class Bad {
public:
~Bad() noexcept(false) {
throw std::runtime_error("에러");
}
};
최적화
// noexcept는 최적화 가능
void func() noexcept {
// 컴파일러가 스택 되감기 코드 생략 가능
}
// std::vector
std::vector<Widget> vec;
vec.push_back(Widget()); // noexcept 이동이면 이동 사용
실무 패턴
패턴 1: RAII 래퍼
class FileHandle {
FILE* file_;
public:
FileHandle(const char* path) : file_(fopen(path, "r")) {
if (!file_) {
throw std::runtime_error("파일 열기 실패");
}
}
// noexcept 소멸자
~FileHandle() noexcept {
if (file_) {
fclose(file_); // 예외 안던짐
}
}
// noexcept 이동
FileHandle(FileHandle&& other) noexcept
: file_(other.file_) {
other.file_ = nullptr;
}
// 복사 금지
FileHandle(const FileHandle&) = delete;
};
패턴 2: 예외 안전 swap
class Buffer {
std::vector<char> data_;
public:
// noexcept swap
void swap(Buffer& other) noexcept {
data_.swap(other.data_); // vector::swap은 noexcept
}
friend void swap(Buffer& a, Buffer& b) noexcept {
a.swap(b);
}
};
// 사용
Buffer b1, b2;
swap(b1, b2); // 예외 없음 보장
패턴 3: 조건부 noexcept 템플릿
template<typename T>
class Wrapper {
T value_;
public:
// T의 이동 생성자가 noexcept면 noexcept
Wrapper(Wrapper&& other)
noexcept(std::is_nothrow_move_constructible_v<T>)
: value_(std::move(other.value_)) {}
// T의 swap이 noexcept면 noexcept
void swap(Wrapper& other)
noexcept(noexcept(std::swap(value_, other.value_))) {
using std::swap;
swap(value_, other.value_);
}
};
// 사용
Wrapper<std::string> w1, w2;
static_assert(noexcept(w1.swap(w2))); // string::swap은 noexcept
FAQ
Q1: noexcept는 언제 사용하나요?
A:
- 이동 연산: 이동 생성자, 이동 대입 연산자
- swap: 스왑 함수
- 소멸자: 항상 noexcept (암시적)
- 예외 없는 함수: 예외를 던지지 않는 함수
class MyClass {
public:
MyClass(MyClass&&) noexcept; // 이동 생성자
MyClass& operator=(MyClass&&) noexcept; // 이동 대입
void swap(MyClass&) noexcept; // swap
~MyClass() noexcept; // 소멸자 (암시적)
};
Q2: noexcept의 성능 이점은?
A:
- 최적화: 컴파일러가 스택 되감기 코드 생략
- STL 최적화:
std::vector가 noexcept 이동 연산자 사용
class Widget {
public:
// ❌ noexcept 없음
Widget(Widget&& other) {
// std::vector는 복사 사용 (안전)
}
};
std::vector<Widget> vec;
vec.reserve(100); // 복사로 재할당
class Widget {
public:
// ✅ noexcept 추가
Widget(Widget&& other) noexcept {
// std::vector는 이동 사용 (빠름)
}
};
std::vector<Widget> vec;
vec.reserve(100); // 이동으로 재할당
Q3: noexcept 위반 시 어떻게 되나요?
A: std::terminate가 호출되어 프로그램이 종료됩니다.
void func() noexcept {
throw std::runtime_error("에러"); // std::terminate
}
int main() {
func(); // 프로그램 종료
}
Q4: 조건부 noexcept는 어떻게 사용하나요?
A: noexcept(조건) 을 사용합니다. 조건이 true면 noexcept, false면 예외 가능합니다.
template<typename T>
void func(T value) noexcept(noexcept(T(value))) {
T copy(value); // T의 복사 생성자가 noexcept면 noexcept
}
// 또는 type_traits 사용
template<typename T>
void func2(T value) noexcept(std::is_nothrow_copy_constructible_v<T>) {
T copy(value);
}
Q5: 소멸자는 항상 noexcept인가요?
A: 기본적으로 noexcept입니다. 명시적으로 noexcept(false)를 지정하지 않는 한 noexcept입니다.
class MyClass {
public:
~MyClass() { // 암시적 noexcept
// 예외 던지면 std::terminate
}
};
// 명시적 noexcept(false) (권장 안함)
class Bad {
public:
~Bad() noexcept(false) {
throw std::runtime_error("에러");
}
};
Q6: noexcept 연산자는 무엇인가요?
A: 표현식이 noexcept인지 확인하는 연산자입니다. 컴파일 타임에 bool 값을 반환합니다.
void func() noexcept {}
void func2() {}
static_assert(noexcept(func())); // true
static_assert(!noexcept(func2())); // false
// 조건부 noexcept에서 사용
template<typename T>
void wrapper(T value) noexcept(noexcept(T(value))) {
T copy(value);
}
Q7: noexcept는 함수 타입의 일부인가요?
A: C++17부터 함수 타입의 일부입니다. C++14까지는 아니었습니다.
// C++17:
void (*ptr1)() noexcept = func; // OK
void (*ptr2)() = func; // 에러 (타입 불일치)
// 함수 포인터 타입이 다름
using FuncNoexcept = void(*)() noexcept;
using Func = void(*)();
Q8: noexcept 학습 리소스는?
A:
- “Effective Modern C++” by Scott Meyers (Item 14)
- “C++ Concurrency in Action” by Anthony Williams
- cppreference.com - noexcept
관련 글: exception-handling, move-semantics, exception-safety.
한 줄 요약: noexcept는 함수가 예외를 던지지 않음을 명시하여 최적화를 가능하게 하는 C++11 키워드입니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ noexcept | “예외 명세” 가이드
- C++ Exception Performance | “예외 성능” 가이드
- C++ noexcept 완벽 가이드 | 예외 계약·이동 최적화·프로덕션 패턴 [#42-1]
관련 글
- C++ noexcept 지정자 |
- C++ Exception Performance |
- C++ noexcept 완벽 가이드 | 예외 계약·이동 최적화·프로덕션 패턴 [#42-1]
- C++ async & launch |
- C++ Atomic Operations |