Что такое exception_ptr в C++?

Ответ

std::exception_ptr — это тип из заголовка <exception>, представляющий обёртку с разделяемым владением (shared ownership) для захваченного исключения. Он позволяет сохранять и передавать исключения между контекстами, например, между потоками, для отложенной обработки.

Основное применение:

  • Передача исключений из рабочего потока в главный (например, при использовании std::async).
  • Создание цепочек или графов исключений.
  • Реализация механизмов отложенного проброса.

Ключевые функции:

  • std::current_exception() — захватывает текущее активное исключение и возвращает exception_ptr на него. Если исключения нет, возвращает пустой exception_ptr.
  • std::rethrow_exception(exception_ptr p) — пробрасывает копию исключения, на которое указывает p.
  • std::make_exception_ptr(E e) — создаёт exception_ptr на копию исключения e.

Пример передачи исключения между потоками:

#include <iostream>
#include <thread>
#include <exception>
#include <stdexcept>

void worker(std::exception_ptr& eptr) {
    try {
        // Имитация работы, которая может завершиться ошибкой
        throw std::runtime_error("Ошибка в рабочем потоке");
    } catch (...) {
        eptr = std::current_exception(); // Захватываем исключение для передачи
    }
}

int main() {
    std::exception_ptr eptr;
    std::thread t(worker, std::ref(eptr));
    t.join();

    // Обработка исключения из потока в главном потоке
    if (eptr) {
        try {
            std::rethrow_exception(eptr);
        } catch (const std::exception& e) {
            std::cout << "Поймано исключение из потока: " << e.what() << 'n';
        }
    }
    return 0;
}

Важно: exception_ptr безопасен для копирования и присваивания; ресурсы освобождаются автоматически, когда на исключение не остаётся ссылок.

Ответ 18+ 🔞

Слушай, вот объясню тебе про эту штуку std::exception_ptr, чтобы ты не охуел потом, когда она тебе на ровном месте вылезет. Представь себе, что исключение — это как горячая картошка: поймал в catch — и либо съел (обработал), либо сразу кинул дальше (throw). А что, если тебе надо эту гребаную картошку не сейчас есть, а, скажем, отложить в сторонку и передать другому чуваку, который в другом потоке сидит? Вот тут-то она и пригождается, эта обёрточка.

По сути, это такой умный указатель с разделяемым владением, как shared_ptr, только не на объект в куче, а на захваченное исключение. Засунул в него ошибку — и таскай себе куда хочешь, хоть через всю программу. Главное, потом не забыть вскрыть да посмотреть, что там внутри.

Зачем это, блядь, нужно? Ну, например, ты запустил асинхронную хуйню через std::async, и она там в другом потоке благополучно обосралась. Как главный поток узнает про эту катастрофу? Вот именно — через exception_ptr. Рабочий поток ловит своё исключение, пакует его в эту обёртку и кидает тебе, как вызовом .get() от фьючерса. А ты уж в своём удобном контексте разворачиваешь и разбираешься, что за пиздец произошёл.

Основные инструменты в твои кривые руки:

  • std::current_exception() — это типа сачок. Внутри блока catch (...) вызываешь эту функцию, и она хватает текущее летящее исключение, какое бы оно ни было, и аккуратно кладёт в коробочку exception_ptr. Если сачком махал впустую (не во время обработки исключения), вернёт пустую коробку.
  • std::rethrow_exception(p) — команда "симона говорит". Передаёшь ей свой exception_ptr p, и она заново кидает копию того исключения, что внутри. Будь готов его снова ловить!
  • std::make_exception_ptr(e) — для перфекционистов. Хочешь создать exception_ptr прямо из исключения e, которое у тебя уже есть в руках? Вот тебе функция, без всяких там try/catch.

Смотри, как это выглядит в деле, на живом примере:

#include <iostream>
#include <thread>
#include <exception>
#include <stdexcept>

void worker(std::exception_ptr& eptr) {
    try {
        // Допустим, тут какая-то полезная работа, которая внезапно пошла по пизде
        throw std::runtime_error("Ошибка в рабочем потоке");
    } catch (...) {
        // Хватаем этот летящий пиздец и суём в нашу обёртку для передачи
        eptr = std::current_exception();
    }
}

int main() {
    std::exception_ptr eptr; // Пока тут пусто, терпения ебать ноль
    std::thread t(worker, std::ref(eptr));
    t.join();

    // А вот теперь в главном потоке смотрим, не принёс ли нам worker говна в обёрточке
    if (eptr) {
        try {
            std::rethrow_exception(eptr); // Распаковываем и кидаем
        } catch (const std::exception& e) {
            std::cout << "Поймано исключение из потока: " << e.what() << 'n';
        }
    }
    return 0;
}

Важный момент на посошок: exception_ptr — не одноразовая штука. Его можно копировать, передавать, у него внутри счётчик ссылок. Когда последняя копия умрёт, память за исключением почистят. Удобно, ёпта. Не надо самому с этим ебаться.