(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Что нового в работе с исключениями в C++11

Источник: habrahabr
prograholic

В интернете довольно много говорят о новых возможностях C++11: auto, lambda, variadic templates. Но как-то обошли стороной новые возможности работы с исключениями, которые предоставляет язык и стандартная библиотека.

От предыдущей версии стандарта остался механизм генерации исключений (throw), проверка того, что мы находимся в процессе обработки исключения (std::uncaught_exception), механизм остановки, если исключение не было обработано. Также есть иерархия стандартных исключений на базе класса std::exception.

Новый стандарт добавляет к этим вещам еще несколько сущностей, которые, на мой взгляд, могут существенно упростить работу с исключениями в C++.

exception_ptr

Итак, самое первое, с чем мы можем столкнуться - это std::exception_ptr. Этот тип позволяет в себе хранить исключение абсолютно любого типа. Стандарт не оговаривает каким образом получен этот тип. Это может быть typedef, это может быть реализация класса. Его поведение сходно поведению std::shared_ptr, то есть его можно копировать, передавать в качестве параметра, при этом само исключение не копируется. Основное предназначение exception_ptr - это передача исключений в качестве параметров функции, возможна передача исключений между потоками. Таким образом, объекты данного типа позволяют сделать обработку ошибок более гибкой:
struct some_exception {
    explicit some_exception(int x): v(x) {
        std::cout << " int ctor" << std::endl;
    }

    some_exception(const some_exception & e): v(e.v) {
        std::cout << " copy ctor" << std::endl;
    }

    int v;
};

std::exception_ptr throwExceptionAndCaptureExceptionPtr() {
    std::exception_ptr currentException;
    try {
        const int throwValue = 10;
        std::cout << "throwing           " << throwValue << "..." << std::endl;
        throw some_exception(throwValue);

    } catch (...) {
         currentException = std::current_exception();
    }

    return currentException;
}

void rethrowException(std::exception_ptr ePtr) {
    try {
        if (ePtr) {
            std::rethrow_exception(ePtr);
        }

    } catch (const some_exception & e) {
        std::cout << "catched int value: " << e.v << std::endl;
    }

    std::exception_ptr anotherExceptionPtr = ePtr;
    try {
        if (anotherExceptionPtr) {
            std::rethrow_exception(anotherExceptionPtr);
        }

    } catch (const some_exception & e) {
        std::cout << "catched int value: " << e.v << std::endl;
    }
}

void checkMakeExceptionPtr() {
    std::exception_ptr currentException = std::make_exception_ptr(some_exception(20));
    std::cout << "exception_ptr constructed" << std::endl;

    rethrowException(currentException);
}

void exceptionPtrSample() {
    rethrowException(throwExceptionAndCaptureExceptionPtr());
    checkMakeExceptionPtr();
}

Если мы запустим функцию exceptionPtrSample на выполнение, то увидим примерно следующий результат:
throwing 10…
int ctor
catched int value: 10
catched int value: 10
int ctor
copy ctor
copy ctor
exception_ptr constructed
catched int value: 20
catched int value: 20

Для того чтобы можно было удобно работать с exception_ptr, есть несколько вспомогательных функций:
  • current_exception - данная функция возвращает exception_ptr. Если мы находимся внутри блока catch, то возвращает exception_ptr, который содержит обрабатываемое в данный момент текущим потоком исключение, если вызывать ее вне блока catch, то она вернет пустой объект exception_ptr
  • rethrow_exception - данная функция бросает исключение, которое содержится в exception_ptr. Если входной параметр не содержит исключения (пустой объект), то результат не определен. При этом msvc кидает std::bad_exception, а программа собранная с помощью gcc-4.7.2неожиданно завершается.
  • make_exception_ptr - данная функция, может сконструировать exception_ptr без бросания исключения. Ее предназначение аналогично функции std::make_shared - конструирование объекта. Ее выполнение аналогично функции throwExceptionAndCaptureExceptionPtr.  В реализации от gcc-4.7.2 make_exception_ptr делает два копирования объекта some_exception.

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

Как мне кажется, тип exception_ptr создавался именно для решения проблемы передачи исключений между потоками, поэтому давайте разберемся, каким образом мы можем передать исключение из одного потока в другой:
void worker(std::promise<void> & p) {
    try {
        throw std::runtime_error("exception from thread");
    } catch (...) {
        p.set_exception(std::current_exception());
    }
}

void checkThreadAndException() {
    std::promise<void> p;
    auto result = p.get_future();

    std::thread t(worker, ref(p));
    t.detach();

    try {
        result.get();
    } catch (const std::runtime_error & e) {
        std::cout << "runtime error catched from async worker" << std::endl;
    }
}

Вообще, многопоточность в C++11 - это обширная тема, там есть свои тонкости, нюансы и о них следует писать отдельно. Сейчас мы рассмотрим этот пример только ради передачи исключения. Запускаем функцию worker в отдельном потоке, и эта функция кидает исключение. Объект класса promise позволяет организовать связь между разными потоками, и атомарно передать значения из одного потока в другой (или исключение). В данном примере мы как раз пользуемся методом set_exception, который принимает exception_ptr в качестве параметра. Для того чтобы получить значение, мы создаем объект класса future- это наш result и вызываем метод get. Также необходимо у потока вызвать метод detach или join, так как при разрушении объекта t в деструкторе проверяется, чтобы joinable() == false, иначе вызывается std::terminate. Скорее всего, это связано с тем, чтобы программист не "отпускал потоки на волю", а всегда следил за ними (либо отпускал явно с помощью метода detach)

Отдельно стоит сказать об использовании многопоточности в gcc-4.7. Изначально этот пример у меня не работал (выбрасывал исключение), погуглив, я выяснил, что для использования std::thread необходимо передать линкеру флаг-pthread. Так как я использую CMake в качестве системы сборки, то эта задача упрощается (тут сложность может возникнуть при использовании gcc на разных платформах, например на sparc solaris используется флаг -thread вместо -pthread) - есть специальный CMake модуль Threads, в котором эта проблема решена:

find_package(Threads REQUIRED)
#…
target_link_libraries(cxx_exceptions ${CMAKE_THREAD_LIBS_INIT})

Nested exceptions

Как видно из названия, данный механизм позволяет "прицепить" к кидаемому исключению другие исключения (которые могли быть брошены раньше). Например, если у нас есть своя иерархия исключений, то мы можем ловить все "сторонние" исключения, прицеплять их к своим исключениям, а при обработке своих исключений мы можем выводить доп. информацию, которая к ним "прицеплена" (например, при отладке, мы можем печатать информацию о сторонних исключениях). Хороший пример использования nested exception приведен на cppreference.com, мой пример, отчасти пересекается с ним:
struct third_party_exception {
    explicit third_party_exception(int e) : code(e) {
    }

    int code;
};


void third_party_worker() {
    throw third_party_exception(100);
}

class our_exception : public std::runtime_error {
public:
    our_exception(const std::string & e) : std::runtime_error("our error: " + e) {
    }
};

void ourWorker() {
    try {
        third_party_worker();
    } catch (...) {
        throw_with_nested(our_exception("worker failed"));
    }
}

void exceptionHandler(const our_exception & e, bool catchNested) {
    std::cerr << "our exception catched: " << e.what();

    if (catchNested) {
        try {
            rethrow_if_nested(e);
        } catch (const third_party_exception & e) {
            std::cerr << ", low level reason is: " << e.code;
        }
    }

    std::cerr << std::endl;
}

void exceptionProcessing() {
    try {
        ourWorker();
    } catch (const our_exception & e) {
        exceptionHandler(e, false);
    }

    try {
        ourWorker();
    } catch (const our_exception & e) {
        exceptionHandler(e, true);
    }
}

Итак, у нас есть сторонняя функция, которая кидает исключение, мы можем написать адаптер, который ловит "сторонние" исключения, и из них делает "наше" исключение: в качестве "сторонней" функции и "нашей" функции выступают соответственно third_party_worker и ourWorker. Мы ловим все исключения, и бросаем далее уже наше (our_exception) исключение, при этом, к нему было прицеплено какое-то (мы в принципе можем и не знать какое) "стороннее" исключение. После этого работаем уже с нашими исключениями. При этом, если нам понадобится более детальная информация о том, что происходило на "нижнем" уровне, то мы всегда можем вызвать функциюrethrow_if_nested. Данная функция анализирует, если ли прицепленное (вложенное) исключение, и если есть, то бросает это вложенное исключение. Функция exceptionHandler принимает "наше" исключение и дополнительный флаг, который разрешает или запрещает вывод информации о стороннем исключении. Мы можем управлять выводом сторонних исключений, управляя параметром catchNested, например, из файла конфигурации (или в зависимости от сборки - Release, Debug).

Для работы с nested exception есть один класс и две функции:

  • nested_exception - данный класс "подмешивается" к бросаемому объекту при вызове функции std::throw_with_nested - этот класс также позволяет вернуть вложенное исключение (exception_ptr) с помощью метода nested_ptr. Также этот класс имеет метод rethrow_nested, который бросает вложенное исключение
  • throw_with_nested - данная шаблонная функция принимает объект некоторого типа (назовем его InputType), и бросает объект который является наследником std::nested_exception и нашего InputType (в реализации от gcc - это шаблонный тип, который наследуется от nested_exception и InputType). Таким образом, мы можем ловить как объект нашего типа, так и объект типа nested_exception и уже потом получать наш тип через метод nested_ptr
  • rethrow_if_nested - данная функция определяет, есть ли у объекта вложенное исключение, и если есть, то бросает его. Реализация может использовать dynamic_cast для определения наследования

В принципе, механизм nested exception довольно интересный, хотя реализация может быть довольно тривиальна, ее можно сделать самому, с помощью ранее описанных функций current_exception и rethrow_exception. В той же реализации gcc, класс nested_exception содержит одно поле типаexception_ptr, которое инициализируется в конструкторе с помощью функцииcurrent_exception, а реализация метода rethrow_nested просто вызывает функцию rethrow_exception.

Спецификация noexcept

Данный механизм можно рассматривать как расширенный (и ныне устаревший) механизм throw(). Основное его предназначение как и раньше - гарантия того что, функция не бросит исключение (если гарантия нарушается, то вызывается std::terminate).

Используется этот механизм в двух видах, аналогично старому throw()

void func() noexcept {
//...
}

И в новом виде:

void func() noexcept(boolean_expression_known_at_compile_time) {
//...
}

При этом, если значение выражения вычислено как истина, то функция помечается как noexcept, иначе, такой гарантии нет.
Также есть соответствующий оператор noexcept(expression), который тоже выполняется в compile time, этот оператор возвращает истину, если выражение не бросает исключение:
void noexceptSample() {
    cout << "noexcept int():         " << noexcept(int()) << endl;
    cout << "noexcept vector<int>(): " << noexcept(vector<int>()) << endl;
}

Данный код для gcc-4.7.2 выводит:
noexcept int(): 1
noexcept vector<int>(): 0

Здесь мы видим, что конструктор встроенного типа int не бросает исключение, а конструктор вектора может бросить (не помечен как noexcept). 
Это удобно применять в шаблонном метапрограмировании, используя данный оператор мы можем написать шаблонную функцию, которая в зависимости от параметра шаблона может быть помечена как noexcept или нет:

template <typename InputTypeT>
void func() noexcept(noexcept(InputTypeT())) {
    InputTypeT var;
    /// do smth with var
    std::cout << "func called, object size: " << sizeof(var) << std::endl;
}

void noexceptSample() {
    std::cout << "noexcept int():         " << noexcept(int()) << std::endl;
    std::cout << "noexcept vector<int>(): " << noexcept(std::vector<int>()) << std::endl << std::endl;

    /// @note function is not actually called
    std::cout << "noexcept func<int>:         " << noexcept(func<int>()) << std::endl;
    std::cout << "noexcept func<vector<int>>: " << noexcept(func<std::vector<int>>()) << std::endl;
}

Данный пример выводит:
noexcept int(): 1
noexcept vector<int>(): 0

noexcept func<int>: 1
noexcept func<vector<int>>: 0

Резюме

Стандарт C++11 привнес много нового в обработку ошибок, конечно, ключевой особенностью здесь остается exception_ptr и возможность передачи произвольных исключений как обычные объекты (в функции, передавать исключения между потоками). Раньше, в каждом потоке приходилось писать развесистый try… catch для всех исключений, а этот функционал существенно минимизирует количество try… catch кода.

Также появилась возможность создавать вложенные исключения, в принципе в библиотеке boost есть механизм boost::exception, который позволяет прикреплять к объекту произвольные данные (например коды ошибок, свои сообщения и пр), но он решает другие задачи - передачу произвольных данных внутри исключения.

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

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 06.12.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
TeeBI for RAD Studio Suite with source code single license
ABBYY Lingvo x6 Многоязычная Профессиональная версия, электронный ключ
Quest Software. SQL Navigator for Oracle
Quest Software. Toad for Oracle Development Suite
SAP Crystal Reports 2008 INTL WIN NUL License
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
СУБД Oracle "с нуля"
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Новые материалы
Утиль - лучший бесплатный софт для Windows
Новости мира 3D-ускорителей
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100