Перейти к содержимому

Как вызвать исключение c

  • автор:

Исключения

В процессе работы программы могут возникать различные ошибки. Например, при передаче файла по сети оборвется сетевое подключение или будут введены некорректные и недопустимые данные, которые вызовут падение программы. Такие ошибки еще называются исключениями. Исключение представлякт временный объект любого типа, который используется для сигнализации об ошибке. Цель объекта-исключения состоит в том, чтобы передать информацию из точки, в которой произошла ошибка, в код, который должен ее обработать. Если исключение не обработано, то при его возникновении программа прекращает свою работу.

Например, в следующей программе происходит деление чисел:

#include double divide(int a, int b) < return a / b; >int main() < int x; int y<>; double z ; std::cout

Эта программа успешно скомпилируется, но при ее выполнении возникнет ошибка, поскольку в коде производится деление на ноль, после чего программа аварийно завершится.

С одной стороны, мы можем в функции divide определить проверку и выполнять деление, если параметр b не равен 0. Однако нам в любом случае надо возвращать из функции divide некоторый результат — некоторое число. То есть мы не можем просто написать:

double divide(int a, int b) < if (b) return a / b; else std::cout

И в этом случае нам надо известить систему о возникшей ошибке. Для этого используется оператор throw .

Оператор throw генерирует исключение. Через оператор throw можно передать информацию об ошибке. Например, функция divide могла бы выглядеть следующим образом:

double divide(int a, int b) < if (b) return a / b; throw "Division by zero!"; >

То есть если параметр b равен 0, то генерируем исключение.

Но это исключение еще надо обработать в коде, где будет вызываться функция divide. Для обработки исключений применяется конструкция try. catch . Она имеет следующую форму:

try < инструкции, которые могут вызвать исключение >catch(объявление_исключения)

В блок кода после ключевого слова try помещается код, который потенциально может сгенерировать исключение.

После ключевого слова catch в скобках идет параметр, который передает информацию об исключении. Затем в блоке производится собственно обработка исключения.

Так изменим весь код следующим образом:

#include double divide(int a, int b) < if (b) return a / b; throw "Division by zero!"; >int main() < int x; int y<>; try < double z ; std::cout catch (. ) < std::cout std::cout

Код, который потенциально может сгенерировать исключение — вызов функции divide помещается в блок try.

В блоке catch идет обработка исключения. Причем многоточие в скобках после оператора catch ( catch(. ) ) позволяет обработать любое исключение.

В итоге когда выполнение программы дойдет до строки

double z ;

При выполнении этой строки будет сгенерировано исключение, поэтому последующие инструкции из блока try выполняться не будут, а управление перейдет в блок catch, в котором на консоль просто выводится сообщение об ошибке. После выполнения блока catch программа аварийно не завершится, а продолжит свою работу, выполняя операторы после блока catch:

Error! The End.

Однако в данном случае мы только знаем, что произошла какая-то ошибка, а какая именно, неизвестно. Поэтому через параметр в блоке catch мы можем получить то сообщение, которое передается оператору throw :

#include >iostream < double divide(int a, int b) < if (b) return a / b; throw "Division by zero!"; >int main() < int x; int y<>; try < double z ; std::cout catch (const char* error_message) < std::cout std::cout

С помощью параметра const char* error_message получаем сообщение, которое предано оператору throw, и выводим это сообщение на консоль. Почему здесь мы получаем сообщение об ошибке в виде типа const char* ? Потому что после оператора throw идет строковый литерал, который представляет как раз тип const char* . И в этом случае консольный вывод будет выглядеть следующим образом:

Division by zero! The End.

Таким образом, мы можем узнать суть возникшего исключения. Подобным образом мы можем передавать информацию об исключении через любые типы, например, std::string:

throw std::string;

Тогда в блоке catch мы можем получить эту информацию в виде объекта std::string :

catch (std::string error_message)

Если же исключение не обработано, то вызывается функция std::terminate() (из модуля стандартной библиотеки C++), которая, в свою очередь, по умолчанию вызывает другую функцию — std::abort() (из ), которая собственно и завершает программу.

Существует очень много функций и в стандартной библиотеке С++, и в каких-то сторонних библиотеках. И может возникнуть вопрос, какие из них вызывать в конструкции try-catch, чтобы не столкнуться с необработанным исключением и аварийным завершением программы. В этом случае может помочь прежде всего документация по функции (при ее наличии). Другой сигнал — ключевое слово noexcept , которое при использовании в заголовке функции указывает, что эта функция никогда не будет генерировать исключения. Например:

void print(int argument) noexcept;

Здесь указываем, что функция print() никогда не вызовет исключение. Таким образом, встретив функцию с подобным ключевым словом, можно ожидать, что она не вызовет исключения. И соответственно нет необходимости помещать ее вызов в конструкцию try-catch.

Создание объекта исключения

При обработке исключения стоит помнить, что при передаче объекта оператору throw блок catch получает копию этого объекта. И эта копия существует только в пределах блока catch.

Для значений примитивных типов, например, int , копирование значения может не влиять на производительность программы. Однако при передаче объектов классов издержки могут выше. Поэтому в этом случае объекты обычно передаются по ссылке, например:

#include double divide(int a, int b) < if (b) return a / b; throw std::string; > int main() < int x; int y<>; try < double z ; std::cout catch (const std::string& error_message) // строка передается по ссылке < std::cout std::cout

Обработка и генерация разных типов исключений

Мы можем генерировать и обрабатывать несколько разных исключительных ситуаций. Допустим, нам надо, чтобы при делении делитель был меньше делимого:

#include double divide(int a, int b) < if(!b) // если b == 0 < throw 0; >if(b > a) < throw "The first number is greater than the second one"; >return a / b; > void test(int a, int b) < try < double result ; std::cout catch (int code) < std::cout catch (const char* error_message) < std::cout > int main() < test(100, 20); // 5 test(100, 0); // Error code: 0 test(100, 1000); // The first number is greater than the second one >

В функции divide в зависимости от значения числа b оператору throw передаем либо число:

throw 0;

либо строковый литерал:

throw "The first number is greater than the second one";

Для тестирования функции divide определена другая функция — test, где вызов функции divide() помещен в конструкцию try..catch . Поскольку при генерации исключения мы можем получать ошибку в виде двух типов — int (если b равно 0) и const char* (если b больше a), то для обработки каждого типа исключений определены два разных блока catch:

catch (int code) < std::cout catch (const char* error_message)

В функции main вызываем функцию test, передавая в нее различные числа. При вызове:

test(100, 20); // 5

число b не равно 0 и меньше a, поэтому никаких исключений не возникает, блок try срабатывает до конца, и функция завершает свое выполнение.

При втором вызове

test(100, 0); // Error code: 0

число b равно 0, поэтому генерируется исключение, а в качестве объекта исключения передается число 0. Поэтому при возникновении исключения программа выберет тот блок catch, где обрабатывается исключения типа int:

catch (int code)

При третьем вызове

test(100, 1000); // The first number is greater than the second one

число b больше a, поэтому объект исключения будет представлять строковый литерал или const char* . Поэтому при возникновении исключения программа выберет блок catch, где обрабатывается исключения типа const char*:

catch (const char* error_message)

Таким образом, в данном случае мы получим следующий консольный вывод:

5 Error code: 0 The first number is greater than the second one

Может быть ситуация, когда генерируется исключение внутри конструкции try-catch , и даже есть блок catch для обработки исключений, однако он обрабатывает другие типы исключений:

void test(int a, int b) < try < double result ; std::cout catch (const char* error_message) < std::cout >

Здесь нет блока catch для обработки исключения типа int . Поэтому при генерации исключения:

throw 0;

Программа не найдет нужный блок catch для обработки исключения, и программа аварийно завершит свое выполнение.

try-catch и деструкторы

Стоит отметить, что, если в блоке try создаются некоторые объекты, то при возникновении исключения у них вызываются деструкторы. Например:

#include class Person < public: Person(std::string name) :name< name > < std::cout ~Person() < std::cout void print() < throw "Print Error"; >private: std::string name; >; int main() < try < Person tom< "Tom" >; tom.print(); // Здесь генерируется ошибка > catch (const char* error) < std::cerr >

В классе Person определяет деструктор, который выводит сообщение на консоль. В функции print просто генерируем исключение.

В функции main в блоке try создаем один объект Person и вызываем у него функцию print, что естественно приведет к генерарции исключения и переходу управления программы в блок catch . И если мы посмотрим на консольный вывод

Person Tom created Person Tom deleted Print Error

то мы увидим, что прежде чем начнется обработка исключения в блоке catch, будет вызван деструктор объекта Person.

Исключения в C++: типы, синтаксис и обработка

Разберёмся, для чего нужны исключения в языке C++ и какими они бывают. Изучим синтаксис выбрасывания, обработки и рассмотрим особые случаи.

Поговорим об исключениях в C++, начиная определением и заканчивая грамотной обработкой.

  1. Инструмент программирования для исключительных ситуаций
  2. Исключения: панацея или нет
  3. Синтаксис исключений в C++
  4. Базовые исключения стандартной библиотеки
  5. Заключение

Георгий Осипов

Один из авторов курса «Разработчик C++» в Яндекс Практикуме, разработчик в Лаборатории компьютерной графики и мультимедиа ВМК МГУ

Исключения — важный инструмент в современном программировании. В большинстве источников тема исключений раскрывается не полностью: не описана механика их работы, производительность или особенности языка C++.

В статье я постарался раскрыть тему исключений достаточно подробно. Она будет полезна новичкам, чтобы узнать об исключениях, и программистам с опытом, чтобы углубиться в явление и достичь его полного понимания.

Статья поделена на две части. Первая перед вами и содержит базовые, но важные сведения. Вторая выйдет чуть позже. В ней — информация для более продвинутых разработчиков.

В первой части разберёмся:

  • для чего нужны исключения;
  • особенности C++;
  • синтаксис выбрасывания и обработки исключений;
  • особые случаи, связанные с исключениями.

Также рассмотрим основные стандартные типы исключений, где и для чего они применяются.

Мы опираемся на современные компиляторы и Стандарт C++20. Немного затронем C++23 и даже C++03.

Если вы только осваиваете C++, возможно, вам будет интересен курс «Разработчик C++» в Яндекс Практикуме. У курса есть бесплатная вводная часть. Именно она может стать вашим первым шагом в мир C++. Для тех, кто знаком с программированием, есть внушительная ознакомительная часть, тоже бесплатная.

Инструмент программирования для исключительных ситуаций

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

  • в системе закончилась оперативная память;
  • соединение с сервером внезапно прервалось;
  • пользователь выдернул флешку во время чтения или записи файла;
  • понадобилось получить первый элемент списка, который оказался пустым;
  • формат файла не такой, как ожидалось.

Примеры объединяет одно: возникшая ситуация достаточно редка, и при нормальной работе программы, всех устройств, сети и адекватном поведении пользователя она не возникает.

Хороший программист старается предусмотреть подобные ситуации. Однако это бывает сложно: перечисленные проблемы обладают неприятным свойством — они могут возникнуть практически в любой момент.

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

После выбрасывания в дело вступает системный код, который ищет подходящий обработчик. Особенность в том, что тот, кто выбрасывает исключение, не знает, кто будет его обрабатывать. Может быть, что и вовсе никто — такое исключение останется сиротой и приведёт к падению программы.

Если обработчик всё же найден, то он ловит (catch) исключение и программа продолжает работать как обычно. В некоторых языках вместо catch используется глагол except (исключить).

Обработчик ловит не все исключения, а только некоторые — те, что возникли в конкретной части определённой функции. Эту часть нужно явно обозначить, для чего используют конструкцию try (попробовать). Также обработчик не поймает исключение, которое ранее попало в другой обработчик. После обработки исключения программа продолжает выполнение как ни в чём не бывало.

Исключения: панацея или нет

Перед тем как совершить операцию, нужно убедиться, что она корректна. Если да — совершить эту операцию, а если нет — выбросить исключение. Так делается в некоторых языках, но не в C++. Проверка корректности — это время, а время, как известно, деньги. В C++ считается, что программист знает, что делает, и не нуждается в дополнительных проверках. Это одна из причин, почему программы на C++ такие быстрые.

Но за всё нужно платить. Если вы не уследили и сделали недопустимую операцию, то в менее производительных языках вы получите исключение, а в C++ — неопределённое поведение. Исключение можно обработать и продолжить выполнение программы. Неопределённое поведение гарантированно обработать нельзя.

Но некоторые виды неопределённого поведения вполне понятны и даже могут быть обработаны. Это зависит от операционной системы:

  • сигналы POSIX — низкоуровневые уведомления, которые отправляются программе при совершении некорректных операций и в некоторых других случаях;
  • структурированные исключения Windows (SEH) — специальные исключения, которые нельзя обработать средствами языка.

Особенность C++ в том, что не любая ошибка влечёт исключение, и не любую ошибку можно обработать. Но если для операции производительность не так критична, почему бы не сделать проверку?

У ряда операций в C++ есть две реализации. Одна супербыстрая, но вы будете отвечать за корректность, а вторая делает проверку и выбрасывает исключение в случае ошибки. Например, к элементу класса std::vector можно обратиться двумя способами:

  • vec[15] — ничего не проверяет. Если в векторе нет элемента с индексом 15, вы получаете неопределённое поведение. Это может быть сигнал SIGSEGV, некорректное значение или взрыв компьютера.
  • vec.at(15) — то же самое, но в случае ошибки выбрасывается исключение, которое можно обработать.

В C++ вам даётся выбор: делать быстро или делать безопасно. Часто безопасность важнее, но в определённых местах программы любое промедление критично.

Синтаксис исключений в C++

Ловим исключения

Начнём с примера:

void SomeFunction() < DoSomething0(); try < SomeClass var; DoSomething1(); DoSomething2(); // ещё код cout catch(ExceptionType e) < std::cout std::cout

В примере есть один try -блок и один catch -блок. Если в блоке try возникает исключение типа ExceptionType , то выполнение блока заканчивается. При этом корректно удаляются созданные объекты — в данном случае переменная var . Затем управление переходит в конструкцию catch . Сам объект исключения передаётся в переменную e . Выводя e.what() , мы предполагаем, что у типа ExceptionType есть метод what .

Если в блоке try возникло исключение другого типа, то управление также прервётся, но поиск обработчика будет выполняться за пределами функции SomeFunction — выше по стеку вызовов. Это также касается любых исключений, возникших вне try -блока.

Во всех случаях объект var будет корректно удалён.

Исключение не обязано возникнуть непосредственно внутри DoSomething*() . Будут обработаны исключения, возникшие в функциях, вызванных из DoSomething* , или в функциях, вызванных из тех функций, да и вообще на любом уровне вложенности. Главное, чтобы исключение не было обработано ранее.

Ловим исключения нескольких типов

Можно указать несколько блоков catch , чтобы обработать исключения разных типов:

void SomeFunction() < DoSomething0(); try < DoSomething1(); DoSomething2(); // ещё код >catch(ExceptionType1 e) < std::cout catch(ExceptionType2 e) < std::cout // ещё код > 

Ловим все исключения

void SomeFunction() < DoSomething0(); try < DoSomething1(); DoSomething2(); // ещё код >catch(. ) < std::cout // ещё код > 

Если перед catch(. ) есть другие блоки, то он означает «поймать все остальные исключения». Ставить другие catch -блоки после catch(. ) не имеет смысла.

Перебрасываем исключение

Внутри catch(. ) нельзя напрямую обратиться к объекту-исключению. Но можно перебросить тот же объект, чтобы его поймал другой обработчик:

void SomeFunction() < DoSomething0(); try < DoSomething1(); DoSomething2(); // ещё код >catch(. ) < std::cout // ещё код > 

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

Перебросить исключение можно другим способом:

std::rethrow_exception(std::current_exception()) 

Этот способ обладает дополнительным преимуществом: можно сохранить исключение и перебросить его в другом месте. Однако результат std::current_exception() — это не объект исключения, поэтому его можно использовать только со специализированными функциями.

Принимаем исключение по ссылке

Чтобы избежать лишних копирований, можно ловить исключение по ссылке или константной ссылке:

void SomeFunction() < DoSomething0(); try < DoSomething1(); DoSomething2(); // ещё код >catch(ExceptionType& e) < std::cout catch(const OtherExceptionType& e) < std::cout > 

Это особенно полезно, когда мы ловим исключение по базовому типу.

Выбрасываем исключения

Чтобы поймать исключение, нужно его вначале выбросить. Для этого применяется throw.

Если throw используется с параметром, то он не перебрасывает исключение, а выбрасывает новое. Параметр может быть любого типа, даже примитивного. Использовать такую конструкцию разрешается в любом месте программы:

void ThrowIfNegative(int x) < if (x < 0) < // выбрасываем исключение типа int throw x; >> int main() < try < ThrowIfNegative(10); ThrowIfNegative(-15); ThrowIfNegative(0); cout // ловим выброшенное исключение catch(int x) < cout > 

Вывод: «Поймано исключение типа int, содержащее число –15».

Создаём типы для исключений

Выбрасывать int или другой примитивный тип можно, но это считается дурным тоном. Куда лучше создать специальный тип, который будет использоваться только для исключений. Причём удобно для каждого вида ошибок сделать отдельный класс. Он даже не обязан содержать какие-то данные или методы: отличать исключения друг от друга можно по названию типа.

class IsZeroException<>; struct IsNegativeException<>; void ThrowIfNegative(int x) < if (x < 0) < // Выбрасывается не тип, а объект. // Не забываем скобки, чтобы создать объект заданного типа: throw IsNegativeException(); >> void ThrowIfZero(int x) < if (x == 0) < throw IsZeroException(); >> void ThrowIfNegativeOrZero(int x) < ThrowIfNegative(x); ThrowIfZero(x); >int main() < try < ThrowIfNegativeOrZero(10); ThrowIfNegativeOrZero(-15); ThrowIfNegativeOrZero(0); >catch(IsNegativeException x) < cout catch(IsZeroException x) < cout > 

В итоге будет напечатана только фраза: «Найдено отрицательное число», поскольку –15 проверено раньше нуля.

Ловим исключение по базовому типу

Чтобы поймать исключение, тип обработчика должен в точности совпадать с типом исключения. Например, нельзя поймать исключение типа int обработчиком типа unsigned int .

Но есть ситуации, в которых типы могут не совпадать. Про одну уже сказано выше: можно ловить исключение по ссылке. Есть ещё одна возможность — ловить исключение по базовому типу.

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

class NumericException < public: virtual std::string_view what() const = 0; >// Класс — наследник NumericException. class IsZeroException : public NumericException < public: std::string_view what() const override < return "Обнаружен ноль"; >> // Ещё один наследник NumericException. class IsNegativeException : public NumericException < public: std::string_view what() const override < return "Обнаружено отрицательное число"; >> void ThrowIfNegative(int x) < if (x < 0) < // Выбрасывается не тип, а объект. // Не забываем скобки, чтобы создать объект заданного типа: throw IsNegativeException(); >> void ThrowIfZero(int x) < if (x == 0) < throw IsZeroException(); >> void ThrowIfNegativeOrZero(int x) < ThrowIfNegative(x); ThrowIfZero(x); >int main() < try < ThrowIfNegativeOrZero(10); ThrowIfNegativeOrZero(-15); ThrowIfNegativeOrZero(0); >// Принимаем исключение базового типа по константной ссылке (&): catch(const NumericException& e) < std::cout > 

Выбрасываем исключение в тернарной операции ?:

Напомню, что тернарная операция ?: позволяет выбрать из двух альтернатив в зависимости от условия:

std::cout = 18 ? "Проходите" : "Извините, вход в бар с 18 лет") 

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

int result = y != 0 ? x / y : throw IsZeroException(); 

Это эквивалентно такой записи:

int result; if (y != 0) < result = x / y; >else

Согласитесь, первый вариант лаконичнее. Так можно выбрасывать несколько исключений в одном выражении:

// Вычислим корень отношения чисел: int result = y == 0 ? throw IsZeroException() : x / y < 0 ? throw IsNegativeException() : sqrt(x / y);

Вся функция — try-блок

Блок try может быть всем телом функции:

int SomeFunction(int x) try < return DoSomething(x); >catch(ExceptionType e) < std::cout  

Тут мы просто опустили фигурные скобки функции. По-другому можно записать так:

int SomeFunction(int x) < try < return DoSomething(x); >catch(ExceptionType e) < std::cout > 

Исключения в конструкторе

Есть как минимум два случая возникновения исключений в конструкторе объекта:

  1. Внутри тела конструктора.
  2. При конструировании данных объекта.

В первом случае исключение ещё можно поймать внутри тела конструктора и сделать вид, как будто ничего не было.

Как в С++ обрабатывать ошибки в конструкторах без исключений?

Во втором случае исключение тоже можно поймать, если использовать try-блок в качестве тела конструктора. Однако тут есть особенность: сделать вид, что ничего не было, не получится. Объект всё равно будет считаться недоконструированным:

class IsZeroException<>; // Функция выбросит исключение типа IsZeroException // если аргумент равен нулю. void ThrowIf0(int x) < if (x == 0) < throw IsZeroException(); >> // Класс содержит только одно число. // Он выбрасывает исключение в конструкторе, если число нулевое. class NotNullInt < public: NotNullInt(int x) : x_(x) < ThrowIf0(x_); >private: int x_; > class Ratio < public: // Инициализаторы пишем после try: Ratio(int x, int y) try : x_(x), y_(y) < >catch(IsZeroException e) < std::cout private: int x_; NotNullInt y_; >; int main() < Ratio(10, 15); try < Ratio(15, 0); >catch(. ) < std::cout > 

Тут мы увидим оба сообщения: «Знаменатель дроби не может быть нулём» и «Дробь не построена».

Если объект недоконструирован, то его деструктор не вызывается. Это логичная, но неочевидная особенность языка. Однако все полностью построенные члены – данные объекта будут корректно удалены:

#include class A < public: A() < std::cout ~A() < std::cout private: > class B < public: B() < std::cout ~B() < // Этой надписи мы не увидим: std::cout private: A a; >; int main() < try < B b; >catch (. ) < >> 

Запустим код и увидим такой вывод:

A constructed B constructed A destructed 

Объект типа A создался и удалился, а объект типа B создался не до конца и поэтому не удалился.

Не все исключения в конструкторах можно обработать. Например, нельзя поймать исключения, выброшенные при конструировании глобальных и thread_local объектов, — в этом случае будет вызван std::terminate .

Исключения в деструкторе

В этом разделе примера не будет, потому что исключения в деструкторе — нежелательная практика. Бывает, что язык удаляет объекты вынужденно, например, при поиске обработчика выброшенного исключения. Если во время этого возникнет другое исключение в деструкторе какого-то объекта, то это приведёт к вызову std::terminate .

Более того, по умолчанию исключения в деструкторе запрещены и всегда приводят к вызову std::terminate . Выможете разрешить их для конкретного конструктора — об этом я расскажу в следующей части — но нужно много раз подумать, прежде чем сделать это.

Обрабатываем непойманные исключения

Поговорка «не пойман — не вор» для исключений не работает. Непойманные исключения приводят к завершению программы через std::terminate . Это нештатная ситуация, но можно предотвратить немедленное завершение, добавив обработчик для std::terminate :

int main() < // Запишем обработчик в переменную terminate_handler auto terminate_handler = []() < auto e_ptr = std::current_exception(); if (e_ptr) < try < // Перебросим исключение: std::rethrow_exception(e_ptr); >catch (const SomeType& e) < std::cerr catch (. ) < std::cerr > else < std::cerr // Всё равно завершим программу. std::abort(); >; // Установим обработчик для функции terminate std::set_terminate(terminate_handler); // ….. > 

Однако не стоит надеяться, что программа после обработки такой неприятной ситуации продолжит работу как ни в чём не бывало. std::terminate — часть завершающего процесса программы. Внутри него доступен только ограниченный набор операций, зависящий от операционной системы.

Остаётся только сохранить всё, что можно, и извиниться перед пользователем за неполадку. А затем выйти из программы окончательно вызовом std::abort() .

Базовые исключения стандартной библиотеки

Далеко не всегда есть смысл создавать новый тип исключений, ведь в стандартной библиотеке их и так немало. А если вы всё же создаёте свои исключения, то сделайте их наследниками одного из базовых. Рекомендуется делать все типы исключений прямыми или косвенными наследниками std::exception .

Обратим внимание на одну важную вещь. Все описываемые далее классы не содержат никакой магии. Это обычные и очень простые классы, которые вы могли бы реализовать и самостоятельно. Использовать их можно и без throw , однако смысла в этом немного.

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

#include #include int main() < // Используем std::runtime_error вместо std::string. // Но зачем? std::runtime_error err("Буря мглою небо кроет"); std::cout

Разберём основные типы исключений, описанные в стандартной библиотеке C++.

std::exception

Базовый класс всех исключений стандартной библиотеки. Конструктор не принимает параметров. Имеет метод what() , возвращающий описание исключения. Как правило, используются производные классы, переопределяющие метод what() .

std::logic_error : public std::exception

Исключение типа logic_error выбрасывается, когда нарушены условия, сформулированные на этапе написания программы. Например, мы передали в функцию извлечения квадратного корня отрицательное число или попытались извлечь элемент из пустого списка.

Конструктор принимает сообщение в виде std::string , которое будет возвращаться методом what() .

// класс копилка class Moneybox < public: void WithdrawCoin() < if (coins_ == 0) < throw std::logic_error("В копилке нет денег"); >--coins_; > void PutCoin() < ++coins_; >private: int coins_ = 0; > 

Перечислим некоторые производные классы std::logic_error . У всех них похожий интерфейс.

  • std::invalid_argument. Исключение этого типа показывает, что функции передан некорректный аргумент, не соответствующий условиям.
double GetSqrt(double x) < return x >= 0 ? sqrt(x) : throw std::invalid_argument("Попытка извлечь квадратный корень из отрицательного числа"); > 

Это исключение выбрасывают функции преобразования строки в число, такие как stol , stof , stoul , а также конструктор класса std::bitset :

try < int f = std::stoi("abracadabra"); >catch (std::invalid_argument& ex)
  • std::length_error. Исключение говорит о том, что превышен лимит вместимости контейнера. Может выбрасываться из методов, меняющих размер контейнеров string и vector . Например resize , reserve , push_back .
  • std::out_of_range. Исключение говорит о том, что некоторое значение находится за пределами допустимого диапазона. Возникает при использовании метода at практически всех контейнеров. Также возникает при использовании функций конвертации в строки в число, таких как stol , stof , stoul . В стандартной библиотеке есть исключение с похожим смыслом — std::range_error .

std::runtime_error : public std::exception

std::runtime_error — ещё один базовый тип для нескольких видов исключений. Он говорит о том, что исключение относится скорее не к предусмотренной ошибке, а к выявленной в процессе выполнения.

При этом, если std::logic_error подразумевает конкретную причину ошибки — нарушение конкретного условия, — то std::runtime_error говорит о том, что что-то идёт не так, но первопричина может быть не вполне очевидна.

Интерфейс такой же, как и у logic_error : класс принимает описание ошибки в конструкторе и переопределяет метод what() базового класса std::exception .

class CommandLineParsingError : public std::runtime_error < public: // этой строкой импортируем конструктор из базового класса: using runtime_error::runtime_error; >; class ZeroDenominatorError : public std::runtime_error < public: // используем готовое сообщение: ZeroDenominatorError() : std::runtime_error("Знаменатель не может быть нулём") < >> 

Рассмотрим некоторые важные производные классы:

  • std::regex_error. Исключение, возникшее в процессе работы с регулярными выражениями. Например, при неверном синтаксисе регулярного выражения.
  • std::system_error. Широкий класс исключений, связанных с потоками, вводом-выводом или файловой системой.
  • std::format_error. Исключение, возникшее при работе функции std::format .

std::bad_alloc : public std::exception

У std::exception есть и другие наследники. Самый важный — std::bad_alloc . Его может выбрасывать операция new. Это исключение — слабое место многих программ и головная боль многих разработчиков, ведь оно может возникать практически везде — в любом месте, где есть динамическая аллокация. То есть при:

  • вставке в любой контейнер;
  • копировании любого контейнера, например, обычной строки;
  • создании умного указателя unique_ptr или shared_ptr;
  • копировании объекта, содержащего контейнер;
  • прямом вызове new (надеемся, что вы так не делаете);
  • работе с потоками ввода-вывода;
  • работе алгоритмов;
  • вызове корутин;
  • в пользовательских классах и библиотеках — практически при любых операциях.

При обработке bad_alloc нужно соблюдать осторожность и избегать других динамических аллокаций.

#include #include #include #include int main() < std::vector vec; try < while (true) < vec.push_back(std::string(10000000, 'a')); >> catch (const std::bad_alloc& e) < std::cout > 

Возможный вывод: «Место закончилось после вставки 2640 элементов».

При аллокациях возможна также ошибка std::bad_array_new_length , производная от bad_alloc . Она возникает при попытке выделить слишком большое, слишком маленькое (меньше, чем задано элементов для инициализации) либо отрицательное количество памяти.

Также при аллокации можно запретить new выбрасывать исключение. Для этого пишем (std::nothrow) после new :

int main() < int* m = new (std::nothrow) int [0xFFFFFFFFFFFFFFULL]; std::cout  

В случае ошибки операция будет возвращать нулевой указатель.

bad_alloc настолько сложно учитывать, что многие даже не пытаются это делать. Мотивация такая: если память закончилась, то всё равно программе делать уже нечего. Лучше поскорей вызвать std::terminate и завершиться.

Заключение

В этой части мы разобрали, как создавать исключения C++, какие они бывают и как с ними работать. Разобрали ключевые слова try , catch и throw .

В следующей части запустим бенчмарк, разберём гарантии безопасности, спецификации исключений, а также узнаем, когда нужны исключения, а когда можно обойтись без них. И главное — узнаем, как они работают.

Исключения не так просты, как кажутся на первый взгляд. Они нарушают естественный ход программы и кратно увеличивают количество возможных путей исполнения. Но без них ещё сложнее.

C++ позволяет выразительно обрабатывать исключения, он аккуратен при удалении всех объектов и освобождении ресурсов. Будьте аккуратны и вы, и тогда всё получится. Каждому исключению — по обработчику.

Исключения — это лишь одна из многих возможностей C++. Глубже погрузиться в язык и узнать больше о нём, его экосистеме и принципах программирования поможет курс «Разработчик C++».

Следите за новыми постами по любимым темам

Подпишитесь на интересующие вас теги, чтобы следить за новыми постами и быть в курсе событий.

Как вызвать исключение c

(C) Dale, 01.02.2011 — 02.02.2011.

Язык C сегодня — довольно неоднозначное явление. Ему уже около 40 лет, а он до сих пор в строю. Второго такого долгожителя вряд ли сыщешь — даже Fortran-IV сдал позиции. Этот язык стал родоначальником множества других, включая столь популярные сегодня C++, Java и C#, по при этом не торопится на пенсию — хотя потомки изрядно его потеснили, но так и не вытеснили полностью.

Конечно, язык, основы которого закладывались еще в конце 1960-х годов, не может быть безупречным по сегодняшним меркам. Хотя он и развивался все это время, о чем свидетельствует последовательность стандартов языка, но в узких рамках совместимости с предыдущими версиями (не забываем, что главный конек C — это совместимость и переносимость), то есть умеренно. Поэтому у него до сих пор есть ряд очевидных слабостей, которые приходится преодолевать программистам.

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

Один из наиболее скользких вопросов программирования на C — это, безусловно, обработка ошибок. Вообще говоря, обработка ошибок — это показатель мастерства и профессиональной зрелости программиста. Плохие программисты пишут программы, которые работают неправильно. Посредственные — пишут программы, которые работают правильно, но в тепличных условиях (оборудование исправно, все файлы на своем месте, данные имеют корректный формат и т.д.). Настоящие мастера пишут программы, которые надежны в любых условиях — если все гладко, они выдают правильный результат, а если по ходу работы возникли проблемы, они либо пытаются бороться с ними по мере возможностей, либо выдают внятную и недвусмысленную диагностику с описанием причин, по которым работа невозможна.

Прямо скажем, «чистый» C — плохой помощник разработчику по части обработки ошибок. В самом языке для этого нет никаких средств, а в стандартных библиотеках нет единодушия по вопросу, каким образом функция должна сообщать клиенту об ошибках при выполнении. Иногда функция возвращает код завершения, он же код ошибки, если имеет ненулевое значение; в других случаях часть диапазона значений функций выделена под коды ошибки; и апофеоз — errno.

Тут впору вспомнить Дейкстру: «. когда я начинаю анализировать свои собственные мыслительные привычки и привычки моих друзей, я прихожу, нравится мне это или нет, к совершенно иному заключению, а именно: инструменты, которые мы пытаемся использовать, и язык и обозначения, которые мы используем для выражения или записи наших мыслей, являются основным фактором, который определяет, о чем мы вообще можем мыслить и что можем выразить! Анализ влияния, которое язык программирования оказывает на своих пользователей, и признание факта, что к настоящему времени мощь нашего мозга является наиболее скудным ресурсом, совместно дают нам новый набор эталонов для сравнения относительных достоинств различных языков программирования» (из статьи « Смиренный программист »). Язык C — не исключение: вызвав потенциально чреватую ошибками функцию, следует проверить код ее завершения (или errno) и запрограммировать действия в случае ошибки; поскольку эти действия сами могут вызвать ошибку, их результат также следует проверить, и. В результате корректная программа оказывается загроможденной кодом обработки ошибок. Менее прилежные программисты, утомившись, просто бросают это дело, и результат в виде вылетов программы, неожиданных результатов и прочих сюрпризов не заставляет себя ждать.

Наследники C, учитывая отрицательный опыт родителя, обзавелись средствами, позволяющими облегчить жизнь программиста при обработке ошибок. В C++ появились исключения, пусть и не слишком изящные, затем в Java и C# эта концепция была доведена до практически приемлемого уровня. Применение механизма исключений позволяет писать код, который решает свою задачу, а в случае, когда продолжение работы невозможно, выбрасывает исключение. В подходящем месте программы размещается перехватчик исключений, который предпринимает соответствующие действия. Структура программы улучшается, т.к. обработка ошибок производится не в том месте, где они возникают, а в том, где это наиболее целесообразно.

К счастью, C располагает ценным средством для компенсации своих многочисленных недостатков и слабостей — препроцессором. Инструмент этот довольно опасен, но при умелом использовании дает замечательные результаты. Сегодня мы рассмотрим одно из таких успешных применений макросов — реализацию механизма исключений на языке ANSI C. А для этого мы попробуем решить очень простую, но весьма типичную задачу.

Рассмотрим простую задачу. Треугольник задан тремя сторонами a, b, c. Нам нужно определить его площадь.

В принципе задача довольно легко решаема:

double triangleArea ( double a , double b , double c )
{
double p = ( a + b + c ) / 2.0 ;
double result = sqrt ( p * ( p - a ) * ( p - b ) * ( p - c ) ) ;
return result ;
}

Впрочем, идеальным это решение не назовешь. Функция реализует вычисление площади по формуле Герона, не заботясь о корректности значений переданных ей параметров. Во-первых, отрицательное значение длины стороны треугольника лишено физического смысла. Во-вторых, не каждые три отрезка положительной длины образуют треугольник. Помимо бессмысленного результата, можно получить и более серьезную проблему в виде отрицательного значения под корнем.

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

Лучше, конечно, проверить параметры внутри функции, до начала вычислений.

double triangleArea ( double a , double b , double c )

Вот только не совсем понятно, что делать, если параметры некорректны. Конечно, можно вернуть нулевое или отрицательное значение вызывающей программе как указание на то, что при выполнении возникла нештатная ситуация:

double triangleArea ( double a , double b , double c )
( a

Как вариант, можно вообще прекратить выполнение программы при ошибке, чтобы заведомо избежать нежелательных последствий:

#include
#include

double triangleArea ( double a , double b , double c )
( a

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

Это одна из причин, по которым язык C сегодня многие не воспринимают всерьез, считая, что на нем невозможно писать изящные программы. Зачастую такое мнение оказывается ошибочным: C может очень многое, гораздо больше, чем кажется на первый взгляд. Нужно лишь воспользоваться одним из многочисленных средств для расширения его возможностей. Одним из таких средств является CException.

Основы CException

Пользоваться CException очень просто. Его основу образуют три макроса: Try, Catch и Throw, а также тип CEXCEPTION_T.

CEXCEPTION_T

Для того, чтобы обработчик ошибки мог выяснить, какая именно ошибка произошла, каждый тип ошибки должен иметь свой идентификатор. Тип этого идентификатора должен быть CEXCEPTION_T. По умолчанию он соответствует unsigned int.

Этот макрос начинает защищенный блок. Синтаксис его подобен синтаксису обычного if: за ним следует либо единичный оператор, либо блок. За ним обязательно должен следовать блок Catch (если вы забудете об этом, получите ошибку компиляции).

Блоки Try могут быть вложены друг в друга. При необходимости можно повторно выбросить исключение, чтобы обработать его во внешнем блоке Try.

Этот макрос начинает блок обработки ошибок. Он выполняется лишь в том случае, если в блоке Try возникло исключение. Исключение может возникнуть как непосредственно в коде внутри блока Try, так и в функции, вызываемой в блоке Try на любом уровне вложенности.

Catch получает идентификатор исключения типа CEXCEPTION_T, который вы можете использовать при обработке ошибки. Если в процессе обработки возникнет исключение, оно будет обработано внешним блоком Try.

Используется для того, чтобы выбросить исключение. Принимает единственный аргумент — идентификатор исключения, который будет передан Catch в качестве причины ошибки. Как уже упоминалось ранее, в процессе обработки ошибки можно опять выбрасывать исключение, в том числе и повторно то же самое, которое обрабатывается в данный момент.

Паттерн обработки ошибок
Располагая описанными выше примитивами, можно написать надежную программу по следующему образцу:

.
// Защищенный блок
Try
.
потенциально_опасный код
if (возникла_ошибка)
Throw(e1); // e1 - код конкретной ошибки
.
вызов_потенциально_опасной_функции()
.
>
// Сюда мы попадаем только в случае возникновения ошибки
Catch(e) // здесь e - код возникшей ошибки
.
код_обработки_ошибки
.
>
// Конец защищенного блока
.

потенциально_опасная_функция()
.
if (возникла_ошибка)
Throw(e2); // e2 - код конкретной ошибки
.
>

Теперь мы можем сосредоточить весь код обработки ошибок в единственном месте — блоке Catch, не загромождая основной поток выполнения программы.

Окончательный вариант программы
Сначала определим коды возможных ошибок, чтобы наша программа не пестрила «магическими числами».

enum ERRORCODE_T
{
NEGATIVE_SIDE ,
BAD_TRIANGLE
} ;

Интерфейс функции вычисления треугольника остается неизменным:

extern double triangleArea ( double a , double b , double c ) ;

А вот в тело функции внесем изменения с учетом того, что узнали про обработку исключений:

#include
#include
#include "CException.h"
#include "ErrorCode.h"
#include "TriangleArea.h"

double triangleArea ( double a , double b , double c )

Ну и в завершение — главная программа:

#include
#include
#include "TriangleArea.h"
#include "CException.h"
#include "ErrorCode.h"

int main ( int argc , char * argv [ ] )
{
CEXCEPTION_T e ;
double a , b , c ;
a = 10.0 ;
b = 2.0 ;
c = 2.0 ;
printf ( "a=%f b=%f c=%f \n " , a , b , c ) ;

Try
{
double area = triangleArea ( a , b , c ) ;
printf ( "area=%f \n " , area ) ;
}
Catch ( e )
{
switch ( e )
{
case NEGATIVE_SIDE :
printf ( "One of triangle sides is negative. \n " ) ;
break ;

case BAD_TRIANGLE :
printf ( "Triangle cannot be made of these sides. \n " ) ;
break ;

default :
printf ( "Unknown error: %d \n " , e ) ;
}
}

system ( "PAUSE" ) ;
return 0 ;
}

Улучшение структуры кода сразу бросается в глаза: функция сообщает о возникновении ошибки, а инфраструктура CException передает управление обработчику ошибок без участия программиста. Больше нет необходимости загромождать текст программы условными операторами (не забываем, что по-хорошему программу нужно еще тестировать, а наличие каждого ветвления усложняет эту процедуру).

Заключение

Конечно, CException — не панацея, и его применение не решит всех проблем. Например, CEXCEPTION_T — это всего лишь целочисленная константа, и с ее помощью невозможно сообщить обработчику все детали об ошибке. Кроме того, обработчик Catch в отличие от настоящих обработчиков, встроенных в современные языки программирования, перехватывает все исключения, поэтому вполне возможно, что некоторые из них, не предназначенные данному обработчику, придется выбросить повторно. Но все же плюсов от использования CException гораздо больше чем минусов, и программы на C, написанные с использованием механизма исключений, окажутся проще и надежнее своих старорежимных собратьев.

Перехват исключений в коде

Область применения: управляемое приложение, мобильное приложение, обычное приложение.

1. В общем случае не рекомендуется перехватывать исключения. В частности не нужно перехватывать исключения только ради выдачи сообщения об ошибке. Необработанное исключение в любом случае будет выдано пользователю в виде сообщения об ошибке (а также будет автоматически записано в журнал регистрации для администратора и отправлено в сервис регистрации ошибок).

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

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

3. Частные случаи некорректного использования и перехвата исключений.

Область применения (уточнение): управляемое приложение, обычное приложение.

3.1. Если имеется некоторая серверная бизнес-логика, которая вызывается с клиента при интерактивной работе пользователя:

&НаСервере
Процедура ВыполнитьОперацию()
// код, приводящий к вызову исключения
.
КонецПроцедуры

то неправильно маскировать от пользователя и администратора исходную проблему:

// на клиенте
Попытка
ВыполнитьОперацию();
Исключение
ПоказатьПредупреждение(,НСтр("ru = 'Операция не может быть выполнена.'"));
КонецПопытки;

Правильно записывать в журнал регистрации подробное представление исключения, а краткое представление добавлять в текст сообщения пользователю:

и тогда на клиенте:

Попытка
ВыполнитьОперацию();
Исключение
ТекстСообщения = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ПоказатьПредупреждение(,НСтр("ru = 'Операция не может быть выполнена по причине:'") + Символы.ПС + ТекстСообщения);
КонецПопытки;

Не следует использовать функцию ОписаниеОшибки , т.к. она неинформативна для разработчика, потому что не возвращает стек в тексте ошибки.

3.2. Не следует анализировать текст исключений с целью интерпретации причины ошибки. Текст исключения может меняться в зависимости от локализации. В условиях отсутствия штатных средств (например, типизированных исключений), следует выдавать пользователю тексты исключений «как есть». Для понятности, его можно дополнить пояснением возможных причин.
Например:

Попытка
ЗагрузитьФайлИзИнтернета(. );
Исключение
ТекстСообщения = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ТекстСообщения = НСтр("ru = 'Не удалось загрузить файл:'") + Символы.ПС + ТекстСообщения + Символы.ПС + НСтр("ru = 'Возможные причины:
• Нет подключения к Интернету;
• На веб-узле возникли неполадки;
• Брандмауэр или другое промежуточное ПО (антивирусы и т.п.) блокируют попытки программы подключиться к Интернету;
• Подключение к Интернету выполняется через прокси-сервер, но его параметры не заданы в программе.'");
ПоказатьПредупреждение(,ТекстСообщения);
КонецПопытки;

В тех случаях, когда анализ типов исключений критически важен для корректной работы бизнес-логики, следует отказаться от исключений и использовать коды ошибок (коды возврата). При этом недопустимо использовать числовые коды ошибок, т.к. код становится нечитаемым:

КодОшибки = ЗагрузитьФайлИзИнтернета(. );
Если КодОшибки = 12345 Тогда
.
ИначеЕсли .

правильно применять строковые литералы (например, "Успешно", "НетМестаНаДиске", "Отменено" и т.п.):

РезультатЗагрузки = ЗагрузитьФайлИзИнтернета(. );
Если РезультатЗагрузки = "Успешно" Тогда
.
ИначеЕсли .

Строковые литералы кодов ошибок не локализуются.

Исключение составляют случаи работы с веб-сервисами и другими внешними системами, где коды ошибок не доступны, а результат работы транслируется в вызывающий код прикладной конфигурации в виде исключений.

3.3. Если имеется некоторая клиентская бизнес-логика (код выполняется полностью на клиенте):

&НаКлиенте
Процедура СоздатьФайлНаДиске()
// код, приводящий к вызову исключения
.
КонецПроцедуры

то рекомендуется делать дополнительный серверный вызов для протоколирования неудачного результата операции в журнал регистрации:

Попытка
// клиентский код, приводящий к вызову исключения
СоздатьФайлНаДиске();
Исключение
ТекстСообщения = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ПоказатьПредупреждение(,НСтр("ru = 'Операция не может быть выполнена по причине:'") + Символы.ПС + ТекстСообщения);
ЗаписатьОшибкуРаботыСФайлами(ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
КонецПопытки;

3.4. Недопустимо перехватывать любые исключения, бесследно для системного администратора:

Попытка
// код, приводящий к вызову исключения
.
Исключение // перехват любых исключений
КонецПопытки;

Как правило, подобная конструкция скрывает реальную проблему, которую впоследствии невозможно диагностировать.
Правильно:

Попытка
// код, приводящий к вызову исключения
.
Исключение
// Пояснение причин перехвата всех исключений "незаметно" от пользователя.
// .
// И запись события в журнал регистрации для системного администратора.
ЗаписьЖурналаРегистрации(НСтр("ru = 'Выполнение операции'"),
УровеньЖурналаРегистрации.Ошибка.
ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

3.5. Неприемлемо в событиях ПриЗаписи , ПередЗаписью , ПередУдалением , ОбработкаПроведения , ОбработкаПроверкиЗаполнения и т.п. вызывать исключения для выдачи останавливающих предупреждений, которые должен отработать пользователь. Вместо этого следует устанавливать параметр Отказ в значение Истина и выводить сообщения пользователю. Тем самым пользователь увидит все блокирующие причины остановки сразу, а не по очереди.

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

Процедура ПередЗаписью(Отказ)
Если Не ЗарегистрироватьИзмененияНаУзлахПлановОбмена() Тогда
ВызватьИсключение НСтр("ru = 'Не удалось зарегистрировать изменения на узлах планов обмена. Обратитесь к администратору.'");
КонецЕсли;
.
КонецПроцедуры

Подробнее см. пп. 1.1 и 1.3 стандарта Информирование пользователя.

3.6. Недопустимо делать проверки наличия у объекта реквизитов, методов, макетов и т.п., используя для этого исключения, т.к. это может привести к сложно диагностируемым ошибкам, а также затрудняет отладку в режиме «Останавливаться по ошибке».
Вместо перехвата исключений в этом случае рекомендуется:

  • использовать механизмы работы с метаданными, чтобы явным образом проверять наличие или отсутствие реквизита (макета и т.п.);
  • если различия связаны с особенностями встраивания библиотек – описывать особенности явным образом в переопределяемых модулях (см. Переопределяемые и поставляемые объекты);
  • пересмотреть логику работы методов, использующих перехват исключений. Например, можно предусмотреть параметры, которые определяются в вызывающем коде и указывают нужно или нет обращаться к какому-либо методу или свойству объекта.

Попытка
КонтекстЭДОСервер.ПолучитьМакет("КомпонентаОбмена");
ПутьВК = КонтекстЭДОСервер.ПутьКОбъекту + ".Макет.КомпонентаОбмена";
Исключение
КонецПопытки;

МакетКомпонентыОбмена = КонтекстЭДОСервер.Метаданные().Макеты.Найти("КомпонентаОбмена");
Если МакетКомпонентыОбмена <> Неопределено Тогда
ПутьКМакету = КомпонентаОбмена.ПолноеИмя()
КонецЕсли;

3.7. Порядок обработки исключений при использовании транзакций описан в стандарте Транзакции: правила использования.

3.8. Неправильно использовать исключения для приведения значения к типу. Для таких операций необходимо использовать возможности объекта ОписаниеТипов .

Попытка
КоличествоДнейРазрешения = Число(Значение);
Исключение
КоличествоДнейРазрешения = 0; // значение по умолчанию
КонецПопытки;

ОписаниеТипа = Новый ОписаниеТипов("Число");
КоличествоДнейРазрешения = ОписаниеТипа.ПривестиЗначение(Значение);

См. также

  • Информирование пользователя
  • Работа с параметром «Отказ» в обработчиках событий
  • Сообщения пользователю

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Luxury Car Rental in Miami | Reserve a Premium Vehicle with RealCar