← Назад к вопросам

Как можно сохранить исключение?

1.7 Middle🔥 121 комментариев
#Исключения и обработка ошибок#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Сохранение исключения для последующей обработки

Сохранение исключения — это процесс захвата информации об исключении в блоке catch, чтобы использовать эту информацию позже, вне области, где исключение было выброшено.

Основной механизм: std::exception_ptr

std::exception_ptr — это умный указатель на исключение, который позволяет сохранить исключение и передать его в другой контекст.

#include <exception>

std::exception_ptr saved_exception = nullptr;

try {
    throw std::runtime_error("Something went wrong");
} catch (...) {
    saved_exception = std::current_exception();  // Сохраняем
}

if (saved_exception) {
    try {
        std::rethrow_exception(saved_exception);
    } catch (const std::runtime_error& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }
}

Практический пример: Асинхронные операции

Сценарий: Запущена асинхронная операция в отдельном потоке, которая может выбросить исключение. Нужно обработать его в основном потоке.

class AsyncTask {
    std::exception_ptr error_state;
    std::thread worker;
    
public:
    AsyncTask() {
        worker = std::thread([this]() {
            try {
                do_heavy_work();  // Может выбросить
            } catch (...) {
                error_state = std::current_exception();
            }
        });
    }
    
    void wait_for_completion() {
        worker.join();
        
        if (error_state) {
            try {
                std::rethrow_exception(error_state);
            } catch (const std::exception& e) {
                std::cerr << "Task failed: " << e.what() << std::endl;
            }
        }
    }
};

Сохранение с контекстом

struct ErrorSnapshot {
    std::string error_message;
    std::exception_ptr exception;
    std::string context_info;
    int error_code;
};

ErrorSnapshot snapshot;

try {
    process_data(huge_dataset);
} catch (const std::exception& e) {
    snapshot.error_message = e.what();
    snapshot.exception = std::current_exception();
    snapshot.context_info = "Processing record #" + std::to_string(current_id);
    snapshot.error_code = -1;
}

// Позже, например в логе
if (snapshot.exception) {
    try {
        std::rethrow_exception(snapshot.exception);
    } catch (const std::exception& e) {
        log_to_file(snapshot.context_info + ": " + e.what());
    }
}

Очередь исключений

template<typename T>
class SafeExceptionQueue {
    std::queue<std::pair<std::exception_ptr, std::string>> queue;
    std::mutex mtx;
    
public:
    void capture_exception(const std::string& context) {
        std::lock_guard<std::mutex> lock(mtx);
        queue.push({std::current_exception(), context});
    }
    
    void process_all() {
        std::lock_guard<std::mutex> lock(mtx);
        
        while (!queue.empty()) {
            auto [exc, ctx] = queue.front();
            queue.pop();
            
            try {
                std::rethrow_exception(exc);
            } catch (const std::exception& e) {
                std::cerr << "Context: " << ctx << " => " << e.what() << std::endl;
            }
        }
    }
};

Result паттерн (функциональный подход)

template<typename T>
class Result {
    std::optional<T> value;
    std::exception_ptr error;
    
public:
    static Result success(const T& v) {
        Result r;
        r.value = v;
        return r;
    }
    
    static Result failure() {
        Result r;
        r.error = std::current_exception();
        return r;
    }
    
    bool is_ok() const { return value.has_value(); }
    bool is_error() const { return error != nullptr; }
    
    T get_value() const { return value.value(); }
    
    void rethrow_if_error() const {
        if (error) std::rethrow_exception(error);
    }
};

Result<int> safe_parse(const std::string& str) {
    try {
        return Result<int>::success(std::stoi(str));
    } catch (...) {
        return Result<int>::failure();
    }
}

Result<int> res = safe_parse("not a number");
if (res.is_error()) {
    res.rethrow_if_error();
}

RAII с сохранением ошибки

class DatabaseTransaction {
    Database& db;
    std::exception_ptr transaction_error;
    bool committed = false;
    
public:
    DatabaseTransaction(Database& d) : db(d) { db.begin(); }
    
    void execute(const std::string& sql) {
        try {
            db.execute(sql);
        } catch (...) {
            transaction_error = std::current_exception();
        }
    }
    
    void commit() {
        if (transaction_error) {
            db.rollback();
            std::rethrow_exception(transaction_error);
        }
        db.commit();
        committed = true;
    }
    
    ~DatabaseTransaction() {
        if (!committed) {
            try {
                db.rollback();
            } catch (...) {}
        }
    }
};

Сохранение с метаданными

struct CapturedError {
    std::exception_ptr exception;
    std::string what_message;
    std::string type_name;
    std::chrono::system_clock::time_point timestamp;
    std::string source_file;
    int source_line;
};

#define SAVE_EXCEPTION(container, file, line) \
    try {} catch (...) { \
        CapturedError err; \
        err.exception = std::current_exception(); \
        try { std::rethrow_exception(err.exception); } \
        catch (const std::exception& e) { err.what_message = e.what(); } \
        catch (...) { err.what_message = "Unknown exception"; } \
        err.timestamp = std::chrono::system_clock::now(); \
        err.source_file = file; \
        err.source_line = line; \
        container.push_back(err); \
    }

std::vector<CapturedError> errors;

try {
    dangerous_operation();
} catch (...) {
    SAVE_EXCEPTION(errors, __FILE__, __LINE__);
}

Преимущества сохранения

1. Отложенная обработка — обработаем ошибку когда удобно 2. Логирование — сохраним в БД или файл 3. Multithread — передадим ошибку из рабочего потока 4. Транзакции — откатим и затем обработаем 5. Агрегирование — соберём несколько ошибок

Ограничения

  • exception_ptr работает только в catch блоке
  • Нельзя выбрасывать из деструктора
  • Overhead памяти при сохранении большого количества

Резюме

Сохранение исключения через std::exception_ptr позволяет:

  1. Обработать ошибку позже
  2. Передать ошибку между потоками
  3. Логировать с полным контекстом
  4. Реализовать функциональные паттерны (Result/Optional)
  5. Гарантировать очистку ресурсов (RAII)
Как можно сохранить исключение? | PrepBro