C++.Бархатный путь



Ошибки и исключительные ситуации - часть 3


Функция exDiv может быть модифицирована следующим образом: возвращаемое целочисленное значение сообщает о ходе вычисления, а непосредственно сам результат вычисления передаётся по ссылке.

Подобная схема противодействия исключительным ситуациям уже применялась при работе со стеком.

#include <iostream.h> int exDiv (float, float, float&); void main() { float val1, val2, resDiv; ::::: if (!exDiv(val1, val2, resDiv)) { ::::: cout << "exDiv error…"; ::::: } } int exDiv (float keyVal1, float keyVal2, float& keyRes) { if (val2) {keyRes = keyVal1/keyVal2; return 1;} return 0; }

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

#include <iostream.h> class DivAnsver { public: int res; float fValue; // Конструктор. DivAnsver(): res(1), fValue(0.0) {}; // ctorИнициализаторы в действии! }; DivAnsver exDiv (float, float); void main() { DivAnsver Ansver; Ansver = exDiv(0.025, 0.10); cout << Ansver.fValue << "..." << Ansver.res << endl; Ansver = exDiv(0.025, 0.0); cout << Ansver.fValue << "..." << Ansver.res << endl; } DivAnsver exDiv (float val1, float val2) { DivAnsver Ans; if (val2) Ans.fValue = val1/val2; else Ans.res = 0; return Ans; }

Функция exDiv возвращает значение объекта Ans (предопределённый конструктор копирования об этом позаботится). При этом, если деление возможно, значение данного-члена res оказывается равным единице, а fValue принимает значение частного от деления. В противном случае res устанавливается в нуль и объект Ans становится исключением.

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

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

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

Казалось бы, всё хорошо и на этом можно было бы остановиться. Однако, нет пределов совершенству!

Существует целый ряд проблем, связанных с подобным способом организации программного кода. Рассмотрим некоторые из них.

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

С ростом числа вариантов возвращаемых значений становится всё более актуальной проблема разделения "положительных" и "отрицательных" ответов.

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

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




Содержание  Назад  Вперед