fopen()
:
FILE *f=...
f=fopen("...","r"); // - пытаемся открыть файл на чтение.
При вызове fopen()
файл мог не открыться по многим причинам, например:
if(f==0) {
... //что-то сделать.
}
fopen()
записывает в глобальную переменную errno
так называемый код ошибки. В этом случае при обработке ошибки с помощью if(f==0)
нужно писать так:
if(f==0) {
switch(errno) {
...
}
}
errno
является глобальной, то существует ряд проблем с этим связанных:
errno
.fopen()
.XML_Parse()
возвращает код ошибки (XML_ERROR_NONE, ...).
if(XML_Parse(...) != XML_ERROR_NONE) {
...
}
Даже авторы библиотеки expat в своих примерах проверяют лишь наличие ошибки, а не её тип. Зачастую проверка не делается вовсе.sscanf()
вернёт количество успешно прочитанных символов.
atoi("42")
вернёт это число или 0, если произошла ошибка. В этом случае мы не сможем отличить ошибку от простого значения 0.
Рассмотрим сложную функцию,возвращающую некоторый результат.
Например, функцию, которая решает уравнение и возвращает его корень:
double solve_equation(...) {
//действия с параметрами
x = a / b;
}
В момент деления a на b могло произойти деление на 0, поэтому вставим проверку:
double solve_equation(...) {
//действия с параметрами
if(b==0) {
...
}
x = a / b;
}
Непонятно, что нужно вернуть в случае b == 0, и более того, если деление происходит много раз, то эту проверку придется проводить во всех случаях.divide(a,b)
, это было бы приемлемым вариантом.
Однако это не избавило бы нас от проблемы с возвращаемым значением. Эта ситуация является ощутимой проблемой для программиста.divide(a,b)
, чтобы избежать деления на 0:
double divide(a, b) {
if(b==0) {
"Случилась исключительная ситуация!"
// В этом месте необходимо сообщить, что случилось что-то непредвиденное, иными словами "бросить" exception.
}
return a/b;
}
В случае, когда кидаем Exception, происходит аварийный выход, т.е. дальше программа не выполняется.divide
вызвана в функции solve_equation
,
то та тоже прекратит свою работу и так по иерархии вызовов вплоть до функции main
exit()
, однако когда мы "бросаем" exeption, в дальнейшем его можно будет "поймать".
ловим исключение {
solve_equation
}
если поймали {
предпринять такое-то действие
}
Если мы сейчас забудем обработать ошибку, то программа просто завершится. Это лучшее, что можно сделать в этой ситуации. А что касается проблем с многопоточностью и глобальными переменными, то теперь их нет.
В качестве исключения можно передать всё что угодно, любую информацию. На самом деле, с помощью Exception'ов мы решили еще одну проблему:
если функция divide()
совершила аварийную отсановку, мы можем узнать об ошибке и обработать её в main
,
т.е. на 2 или несколько уровней выше (по иерархии вызовов).
Возможность обработки ошибок в любом месте программы является очень важным свойством, т.к. разные куски кода зачастую пишут разные люди и в разное время.
Например, функцию для решения уравнений и графический интерфейс для работы с ней как правило пишет не один и тот же программист.
Если при решении уравнения случилась ошибка,то автор может не знать, что с ней делать. Тогда в этом месте достаточно "кинуть" Exception,
а его обработкой и сообщением пользователю об ошибке будет заниматься человек, использующий эту функцию.
Comments: Без использования исключений код ошибки пришлось бы передавать через всю иерархию вызовов функций, а с помощью exception'ов это происходит автоматически.
Использование Exception'ов является "сильнодействующим средством" для больших программ, поэтому не следует применять их без особой необходимости.
divide
с использованием исключений:
double divide(a, b) {
if(b==0) {
throw std::string("Случилось страшное!");
}
return a/b;
}
После throw выполняется развёртка стека, т.е. последовательный выход из всех функций.solve_equation
вначале был создан некий объект:
double solve_equation(...) {
Complex c;
Array array;
...
return x;
// здесь был бы вызван деструктор
}
В момент, когда мы "бросаем" exception, происходит выход из функции и для всех объектов, созданых на стеке, будет вызван
деструктор. Работа функции завершится корректно.
main
try {
solve_equation() // пытаемся выполнить кусочек кода, если происходит исключение, то ловим его
}
catch( //что ловить// std::string &ex) {
...//что-то сделать с "пойманной" строчкой
}
Comment 1:У одного try
может быть несколько обработок catch
.int
, то исключение не будет поймано и полетит дальше.
А если ловили std::string
, а брошено было "Случилось страшное!" – тоже не будет поймано, т.к. это уже const char *
.std::exception
.
Рекомендуется использовать только его.std::exception
находится один виртуальный метод:
virtual const char *what(); // отвечает на вопрос "что летит?"
От класса std::exception можно отнаследоваться и переопределить этот метод. В случае необходимости можно добавить и свои методы.std::exception
.
catch(std::exception &e) {
// что-то сделать.
}
Если в этом месте программы мы не знаем как правильео обработать данную ошибку или не смогли жто сделать, то можно перебросить exception дальше:
catch(std::exception &e) {
// пытаемся обработать ошибку.
throw e; // если не получилось обработать, то бросить дальше "rethrow".
}
Существует более короткая запись "переброски" exception'а:
catch(std::exception &e) {
throw;
}
Именно такая запись испальзуется в следующем случае:catch(...){//что-то сделать//}
,
но в этом случае у нас нет переменной, с которой можно работать, тогда можно бросить exception дальше без непосредственного обращения к нему.
catch(...) {
//что-то сделать.
throw;
}
Comment 1: В случае если после try
написано несколько catch
, выполняться всегда будет первый подходящий.std::exception
есть несколько стандартных наследников.
Рассмотрим примеры использования подклассов исключений.
Object *o = new Object[1000000000];
В этом случае new
бросит bad_alloc.
(invalid_argument)
.
То, о чём мы предупреждали, но "Вы" всё равно нарвались.Такие ошибки принято ловить и как-то обрабатывать.
sort(int from, int to) { //from < to//
if(from >= to) {
throw logic_error(...);
}
...
}
try {
for(int i = 0; i < 100; ++i ) {
if(a[i] == 0) {
throw std::exception();
}
...
}
}
catch(std::exception &...) {
}
new
в этом случае вернёт 0, а не бросит std::bad_alloc.int data[4]
,
т.к.при использовании массива не ясно что именно хранится в каждом элементе.std::ifstream("file.txt", std::ios::in);
std::ifstream("file.txt");
) облегчит понимание и не изменит работу программы?..
Хорошая программа та, которую легко понять и исправить, а не та, которая правильно, но непонятно как работает!!!std::map< std::string, std::string> Map;
Map.insert(std::pair< std::string, std::string>("first", "second"));
Нужно: Map.insert( std::make_pair("first", "second"));
for(std::list< x>::iterator it = l.begin(); it != l.end(); ++it) {
if(test(*it)) {
it = l.erase(it);
}
}
При удалении элемента следующий будет пропущен.Чтобы избежать этого, после удаления можно написать --it;,
но это усложнит понимание кода и будет некорректно работать при удалении первого элемента.
for(std::list< x>::iterator it = l.begin(); it != l.end(); ) {
if(test(*it)) {
it = l.erase(it);
}
else {
++it;
}
}
const std::string &foo() {
std::string s;
return s; // s - временный объект
}
Вернуть ссылку на объект, находящийся на стеке можно, но если стек не был изменен. Иначе правильная работа программы не гарантирована.foo(int x, int z)
foo(int x, int)
-Wall -Wno -...
if(im < 0) {
os<< abs(im); //зачем вызывать abs от заведомо отрицательного числа?
}
Более простой вариант(нет вызова целой функции!):
if(m<0) {
os<< -im;
}
Вывод: Вывод на экран, конечно, работает гороздо дольше, чем вызов функции abs, но зачем, если хороший код написать проще?
for(i = 0; i < length(); ++i) {
...
}
Здесь в цикле каждый раз вычисляется одно и то же значение функции length(), а значит увеличивается время работы.
Более оптимальный код:
int l=lenght();
for(i = 0; i < l; ++i) {
...
}