CPP-101: Обработка на изключения

Обработка на грешки

Да си представим, че по време на изпълнение дадена функция открива ненормална, грешна ситуация. Причината за възникването на такава ситуация може да бъде различна – неправилни входни данни, препълване на диска, изчерпване на наличната динамична памет, невъзможност да се отвори файл и т.н. По какъв начин функцията трябва да реагира на такава ситуация?

Подходите могат да бъдат различни. Обикновено всеки програмист използва собствен стил за обработка на грешки, което води до голямо разнообразие от програмистки практики. Един от подходите, който сравнително стандартно се използва в езика C, е функцията, открила ненормална ситуация да върне резултат, който сигнализира за наличието на грешка. Голяма част от функциите в стандартната C библиотека са организирани точно по този начин. Например функцията за отваряна на файлове:

FILE* fopen(const char* filename, const char* mode);

връща нулев указател NULL ако не успее да отвори файл със зададеното име. Функциите за писане във файл

int fputc(int c, FILE* file);
int fputs(const char* str, FILE* file);

връщат EOF когато не успеят да запишат данните във файла. По подобен начин работи и функцията за четене от файл:

int fgetc(FILE* file);

При възникване на грешка или достигане до края на файла тази функция връща EOF. В стандартната C библиотека могат да се намерят огромно количество подобни примери.

Имайки предвид тази практика нека разгледаме метода void push() на класа стек от раздел Пример: Стек:

class Stack {
    ...
public:
    ...
	void push(int val) {
        if(top_<STACK_SIZE) {
            data_[top_++]=val;
        }
    }
    ...
};

Когато стекът е пълен, методът void push() по никакъв начин не сигнализира за наличие на грешка. Едно възможно решение на този проблем е да преработим метода void push() така, че да връща стойност – например int, – като ако възникне грешка, методът връща ненулева стойност, а ако всичко е наред, връща нула:

int push(int val) {
    if(top_<STACK_SIZE) {
        data_[top_++]=val;
        return 0;
    }
    return -1;// Грешка: стека е пълен
}

Как обаче можем да се справим с подобен проблем във член-функцията int pop() (вж. раздел Пример: Стек). Член-функцията е дефинирана по следния начин:

class Stack {
    ...
public:
    ...
	int pop(void) {
		if(top_>0) {
			return data_[--top_];
		}
		return 0;
	}
    ...
};

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

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

int pop(int& val) {
    if(top_>0) {
        val=data_[--top_];
        return 0;
    }
    return -1;// Грешка: стека е празен
}

Стойността на елемента от стека се връща като стойност на аргумента val, а резултатът от функцията се разглежда изцяло като код за грешка. Ако резултатът от функцията е нула, то функцията е работила правилно; ако резултатът е ненулев, то е настъпила грешка при изпълнение на функцията.

Разгледаният подход за обработка на грешки макар и често използван е тежък и тромав. Използването му води до това, че при всяко извикване на функция, програмистът трябва да изследва резултата от тази функция за възможни настъпили грешки. Това прави кода на програмата труден за разбиране и поддържане. Друг недостатък на разглеждания подход е, че при него няма стандарти, които да се спазват от всички. При настъпване на грешка може да връща ненулев резултат, друга да връща нула, а трета – отрицателно число. Това прави трудно еднотипното обработване на грешки.

Публикувано в CPP-101, ООП с етикети , , . Постоянна връзка.

Един коментар по CPP-101: Обработка на изключения

  1. Pingback: CPP-101: Кратък обзор на езика за програмиране C++ | Записки по програмиране

Коментарите са затворени.