본문으로 건너뛰기
Previous
Next
C++ Exception Performance: Zero-Cost, noexcept, and Error

C++ Exception Performance: Zero-Cost, noexcept, and Error

C++ Exception Performance: Zero-Cost, noexcept, and Error

이 글의 핵심

C++ exception model: zero-cost on success path, cost of throw and unwind, noexcept and vector moves, frequent errors vs exceptions, and -fno-exceptions.

Introduction

C++ uses a zero-cost exception model on the success path: when no exception is thrown, cost is usually tiny. When an exception is thrown, stack unwinding is expensive. This article surveys behavior and tuning.

1. Zero-cost exception model

Success path vs throw path

#include <iostream>
#include <chrono>
void normalPath() {
    try {
        int x = 42;
        int y = x * 2;
    } catch (...) {
    }
}
void exceptionPath() {
    try {
        throw std::runtime_error("error");
    } catch (...) {
    }
}

Typical measurements show the throw path orders of magnitude slower per iteration than straight-line code—benchmarks depend on compiler, OS, and depth.

Idea behind “zero-cost”

// Compiler / runtime model (simplified):
// 1. Success path: minimal extra code on hot path
// 2. Exception tables: metadata elsewhere
// 3. On throw: table lookup + unwind + dtors

2. Cost breakdown

What a throw does

class Resource {
public:
    Resource() { std::cout << "ctor" << std::endl; }
    ~Resource() { std::cout << "dtor" << std::endl; }
};
void func3() {
    Resource r3;
    throw std::runtime_error("error");
}
void func2() {
    Resource r2;
    func3();
}
void func1() {
    Resource r1;
    func2();
}
int main() {
    try {
        func1();
    } catch (const std::exception& e) {
        std::cout << "caught: " << e.what() << std::endl;
    }
}

Costs include exception object construction, table lookup, unwinding frames, and calling destructors along the way.

Deeper stacks cost more

Throwing through many frames is slower than shallow throws—keep error handling high-level when possible.

3. Error codes vs exceptions

Shape of APIs

#include <optional>
std::optional<int> divideErrorCode(int a, int b) {
    if (b == 0) {
        return std::nullopt;
    }
    return a / b;
}
int divideException(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("divide by zero");
    }
    return a / b;
}

On the success path, well-optimized code with or without try can be similar; on the error path, exceptions are usually far more expensive than returning nullopt or a sentinel.

4. noexcept optimization

noexcept and containers

class Widget {
    int* data;
public:
    Widget() : data(new int(42)) {}
    
    // Potentially-throwing move: vector may copy on growth
    Widget(Widget&& other) {
        data = other.data;
        other.data = nullptr;
    }
    
    // noexcept move: vector can move elements
    Widget(Widget&& other) noexcept {
        data = other.data;
        other.data = nullptr;
    }
    
    ~Widget() { delete data; }
};

5. Common pitfalls

Pitfall 1: Exceptions for control flow

Prefer normal branches (if, break, return) over throw for frequent control transfer.

Pitfall 2: Frequent throws

Parsing or validation in a tight loop should usually return optional, expected, or error codes—not throw on every bad token.

Pitfall 3: Huge exception objects

Keep exception types small; avoid embedding giant buffers in the exception object.

Pitfall 4: -fno-exceptions

Embedded or extreme environments may disable exceptions entirely—use optional, expected, or error codes consistently.

6. Optimization strategies

Use noexcept where true

class Buffer {
    std::vector<int> data;
public:
    Buffer(Buffer&& other) noexcept 
        : data(std::move(other.data)) {}
    
    void swap(Buffer& other) noexcept {
        data.swap(other.data);
    }
    
    ~Buffer() noexcept = default;
};

Minimize throws

Reserve exceptions for exceptional paths (I/O open failure, invariant violation). Use error codes for expected failures (bad user input line).

Document contracts

Mark noexcept on functions that truly cannot throw; use conditional noexcept in templates.

7. Example: file processing

#include <fstream>
#include <string>
#include <optional>
#include <vector>
#include <iostream>
class FileProcessor {
public:
    std::vector<std::string> readLines(const std::string& filename) {
        std::ifstream file(filename);
        if (!file) {
            throw std::runtime_error("open failed: " + filename);
        }
        std::vector<std::string> lines;
        std::string line;
        while (std::getline(file, line)) {
            lines.push_back(line);
        }
        return lines;
    }
    
    std::optional<int> parseLine(const std::string& line) noexcept {
        try {
            return std::stoi(line);
        } catch (...) {
            return std::nullopt;
        }
    }
    
    std::vector<int> processFile(const std::string& filename) {
        auto lines = readLines(filename);
        std::vector<int> numbers;
        for (const auto& line : lines) {
            if (auto num = parseLine(line)) {
                numbers.push_back(*num);
            }
        }
        return numbers;
    }
};

Summary

Key points

  1. Zero-cost: cheap success path in typical implementations
  2. Throw cost: unwind + dtors—expensive
  3. noexcept: enables optimizations and better container behavior
  4. Error codes: better for frequent, expected failures
  5. Exceptions: better for rare, hard failures

Exceptions vs error codes

SituationTypical choiceReason
File missingExceptionRare in steady state
Network downException / typed errorOften rare
Parse failure per lineError code / optionalMay be frequent
Input validationError codeFrequent
OOM (operator new)Exception (unless no-except build)Rare, severe
Out-of-range programmer bugException / assertNot a hot path

Practical principles

  • Do not use exceptions for ordinary control flow
  • Prefer error codes where failures are common
  • Mark noexcept when accurate
  • Keep destructors non-throwing
  • Profile real workloads


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ exception model: zero-cost on success path, cost of throw and unwind, noexcept and vector moves, frequent errors vs … 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, Exceptions, Performance, Optimization, Zero-Cost 등으로 검색하시면 이 글이 도움이 됩니다.