← Назад к вопросам

Для чего нужно исключение?

1.3 Junior🔥 241 комментариев
#Исключения и обработка ошибок

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Назначение исключений (Exception Handling)

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

Основная проблема: возвращаемые коды

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

// Старый подход с кодами ошибок
int file_read(FILE* f, char* buffer, size_t size) {
    if (!f) return -1;           // Ошибка: файл не открыт
    if (size > MAX_SIZE) return -2; // Ошибка: размер слишком большой
    if (fread(buffer, 1, size, f) == 0) return -3; // Ошибка чтения
    return 0; // Успех
}

int result = file_read(f, buf, 1024);
if (result != 0) {
    // Что означает -1? -2? -3? Нужно помнить коды
    // Легко забыть проверить ошибку
}

Проблемы этого подхода:

  • Легко забыть проверить код ошибки
  • Нельзя отличить ошибку от нормального результата (если функция должна возвращать число)
  • Код становится перегруженным if-проверками
  • Трудно пробросить ошибку через несколько уровней функций

Решение: Исключения

// Новый подход с исключениями
void file_read(const string& filename, vector<char>& buffer) {
    ifstream file(filename);
    if (!file.is_open()) {
        throw FileOpenException("Cannot open file: " + filename);
    }
    
    file.read(buffer.data(), buffer.size());
    if (!file) {
        throw ReadException("Error reading file");
    }
}

// Вызывающий код
try {
    file_read("data.txt", buf);
    // Нормальное выполнение
} catch (const FileOpenException& e) {
    cerr << "File error: " << e.what() << endl;
} catch (const ReadException& e) {
    cerr << "Read error: " << e.what() << endl;
}

Преимущества исключений

1. Отделение обработки ошибок от нормального потока

Код логики становится чище:

// С исключениями
void process_user_data(const string& filename) {
    auto data = load_data(filename);      // Выбросит исключение если ошибка
    auto cleaned = clean_data(data);      // Логичный нормальный поток
    save_result(cleaned);
}

// Обработка ошибок отдельно
try {
    process_user_data("input.txt");
} catch (const exception& e) {
    log_error(e.what());
}

2. Иерархия типов исключений

class Exception : public std::exception { };
class FileException : public Exception { };
class IOError : public FileException { };
class CorruptedData : public FileException { };

// Можно перехватить целую категорию
try {
    // ...
} catch (const FileException& e) {  // Перехватит ВСЕ производные
    handle_file_error(e);
} catch (const Exception& e) {       // Fallback для остального
    handle_generic_error(e);
}

3. Гарантия распространения ошибок

Если функция не обработает исключение, оно автоматически пройдёт вверх:

void level3() {
    throw runtime_error("Something wrong");
}

void level2() {
    level3(); // Исключение пройдёт дальше
}

void level1() {
    try {
        level2();
    } catch (const exception& e) {
        // Перехватим здесь, хотя выброшено в level3
        cerr << e.what() << endl;
    }
}

RAII и исключения

Исключения идеально работают с RAII (Resource Acquisition Is Initialization):

class Database {
    Connection* conn;
public:
    Database(const string& url) {
        conn = connect(url);
        if (!conn) throw ConnectionError("Failed to connect");
    }
    
    ~Database() {
        if (conn) disconnect(conn);  // Сработает даже при исключении!
    }
};

void process() {
    Database db("localhost");
    // Если выброшено исключение, деструктор вызовется автоматически
    query_data(db);
}

Когда НЕ использовать исключения

1. Встраиваемые системы (embedded) — часто отключают исключения 2. Очень критичные по задержкам операции — исключения имеют overhead 3. Функции low-level — могут возвращать error code

// Вместо исключений иногда используют optional/result
std::optional<int> parse_int(const string& s) {
    try {
        return stoi(s);
    } catch (...) {
        return std::nullopt;
    }
}

Best Practices

1. Выбрасывай специфичные исключения:

throw FileOpenException(filename);  // ✓
throw Exception();                  // ✗ Слишком общее

2. Лови по const reference:

catch (const MyException& e) { }  // ✓
catch (MyException e) { }         // ✗ Копирует

3. Не выбрасывай исключения в деструкторах:

~MyClass() noexcept {
    // Может привести к terminate() если уже обрабатывается исключение
}

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

Для чего нужно исключение? | PrepBro