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

В чем разница между программой с исключениями и без?

2.0 Middle🔥 121 комментариев
#Исключения и обработка ошибок#Язык C++

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

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

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

Разница между программой с исключениями и без

Исключения (exceptions) в C++ — это механизм обработки ошибок, который коренным образом меняет структуру программы и её поведение при возникновении ошибок. Давайте подробно разберём различия.

1. Обработка ошибок: Коды ошибок vs Исключения

Программа без исключений (старомодный стиль)

// Функция возвращает код ошибки
int divide(int a, int b, int* result) {
    if (b == 0) {
        return -1;  // Ошибка
    }
    *result = a / b;
    return 0;  // OK
}

int main() {
    int res;
    
    // Нужно проверять каждый возврат
    if (divide(10, 2, &res) != 0) {
        std::cerr << "Error!" << std::endl;
        return 1;
    }
    std::cout << "Result: " << res << std::endl;
    
    if (divide(10, 0, &res) != 0) {  // Нужна ещё проверка
        std::cerr << "Error!" << std::endl;
        return 1;
    }
    
    return 0;
}

Программа с исключениями (современный стиль)

// Функция выбрасывает исключение при ошибке
int divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}

int main() {
    try {
        int res1 = divide(10, 2);
        std::cout << "Result: " << res1 << std::endl;
        
        int res2 = divide(10, 0);  // Выбросит исключение
        std::cout << "Result: " << res2 << std::endl;  // Не выполнится
    } catch (const std::invalid_argument& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

2. Контрольный поток (Control Flow)

Без исключений — линейный поток:

status = openFile("data.txt", &file);
if (status != OK) return status;

status = readHeader(&file, &header);
if (status != OK) return status;  // Нужно закрыть файл!

status = parseData(&file, &data);
if (status != OK) return status;  // Нужно закрыть файл!

status = closeFile(&file);
if (status != OK) return status;

return OK;

С исключениями — можно прервать поток:

try {
    File file("data.txt");  // RAII: открывает файл
    Header header = file.readHeader();
    Data data = file.parseData();
    // Файл автоматически закроется при выходе из scope
    return OK;
} catch (const FileException& e) {
    std::cerr << "Error: " << e.what() << std::endl;
    return ERROR;  // Файл уже закрыт благодаря RAII
}

3. RAII (Resource Acquisition Is Initialization)

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

Без исключений (велик риск утечек):

MemoryPool* pool = allocatePool(1000);
Connection* conn = pool->getConnection();

if (someError) {
    delete conn;      // Нужно вручную освобождать
    freePool(pool);
    return;
}

if (anotherError) {
    delete conn;      // Снова нужно освобождать
    freePool(pool);   // Утечка, если забыли
    return;
}

delete conn;
freePool(pool);

С исключениями (безопасно с RAII):

try {
    MemoryPool pool(1000);        // Конструктор выделяет
    Connection conn = pool.getConnection();
    
    if (someError) {
        throw std::runtime_error("Error");
        // pool и conn автоматически разрушаются
    }
    
    if (anotherError) {
        throw std::runtime_error("Another error");
        // pool и conn автоматически разрушаются
    }
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << std::endl;
    // Все ресурсы уже освобождены
}

4. Производительность

Без исключений

  • Нулевая стоимость успешного пути
  • Проверка ошибок на каждом вызове
  • Предсказуемое поведение
for (int i = 0; i < 1000000; ++i) {
    int result;
    if (divide(i, 2, &result) != 0) {  // Проверка на каждой итерации
        break;
    }
}

С исключениями

  • Zero-cost abstraction в успешном пути (исключения не происходят)
  • Overhead при выбросе исключения (stack unwinding)
  • Размер кода может быть больше
try {
    for (int i = 0; i < 1000000; ++i) {
        int result = divide(i, 2);  // Без явной проверки
    }
} catch (const std::exception& e) {
    // Исключение может быть выброшено один раз
    // Но unwinding стоит дорого
}

5. Размер кода и сложность

Без исключений:

status1 = func1();
if (status1) return status1;
status2 = func2();
if (status2) return status2;
status3 = func3();
if (status3) return status3;
// Код быстро становится нечитаемым

С исключениями:

try {
    func1();
    func2();
    func3();
    // Чистый, читаемый код
} catch (...) {
    // Централизованная обработка ошибок
}

6. Типизация ошибок

Без исключений (не типизированно):

int error = someFunction();
if (error == 1) { /* Тип ошибки неясен */ }
if (error == 2) { /* Нужна документация */ }
if (error == -1) { /* Легко перепутать коды */ }

С исключениями (типизированно):

try {
    someFunction();
} catch (const FileNotFoundException& e) {
    std::cout << "File not found: " << e.what();
} catch (const PermissionDeniedException& e) {
    std::cout << "Permission denied: " << e.what();
} catch (const std::exception& e) {
    std::cout << "Unknown error: " << e.what();
}

7. Глубокие стеки вызовов

Без исключений (распространение ошибок):

int level3() {
    int res = database->query();
    if (res != OK) return res;  // Нужно вернуть ошибку
    return OK;
}

int level2() {
    int res = level3();
    if (res != OK) return res;  // Нужно пробросить ошибку
    return OK;
}

int level1() {
    int res = level2();
    if (res != OK) return res;  // Нужно пробросить ошибку
    return OK;
}

int main() {
    int res = level1();
    if (res != OK) {
        // Обработка
    }
}

С исключениями (автоматическое распространение):

void level3() {
    database->query();  // Ошибка автоматически распространяется
}

void level2() {
    level3();  // Без проверок
}

void level1() {
    level2();  // Без проверок
}

int main() {
    try {
        level1();
    } catch (const std::exception& e) {
        // Обработка в одном месте
    }
}

8. Надёжность и безопасность

Без исключений (можно забыть проверку):

void processData() {
    Data data = readData();  // Ошибка? Неизвестно
    // Если разработчик забыл проверить, программа продолжит работу с мусором
}

С исключениями (ошибка не может быть проигнорирована):

void processData() {
    Data data = readData();  // Если ошибка, поток прервётся
    // Ошибку ОБЯЗАТЕЛЬНО нужно обработать где-то выше
}

9. Примеры с подробным кодом

Обработка файлов без исключений

bool processFile(const std::string& filename) {
    FILE* file = fopen(filename.c_str(), "r");
    if (!file) {
        std::cerr << "Cannot open file";
        return false;  // Ошибка открытия
    }
    
    int count = 0;
    while (fgets(buffer, sizeof(buffer), file) != nullptr) {
        if (!processLine(buffer)) {
            fclose(file);  // Нужно закрыть!
            return false;  // Ошибка обработки строки
        }
        count++;
    }
    
    if (fclose(file) != 0) {
        std::cerr << "Error closing file";
        return false;  // Ошибка закрытия
    }
    
    std::cout << "Processed " << count << " lines";
    return true;
}

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

void processFile(const std::string& filename) {
    try {
        std::ifstream file(filename);  // RAII
        if (!file) {
            throw std::runtime_error("Cannot open file: " + filename);
        }
        
        std::string line;
        int count = 0;
        while (std::getline(file, line)) {
            processLine(line);  // Если ошибка, выбросит исключение
            count++;
        }
        
        std::cout << "Processed " << count << " lines";
        // Файл автоматически закроется
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        // Файл уже закрыт
        throw;  // Пробросить исключение выше если нужно
    }
}

Выводы

Выбирайте исключения если:

  • Разрабатываете высокоуровневое приложение
  • Нужна чистота и читаемость кода
  • Работаете с глубокими стеками вызовов
  • Используете RAII парадигму

Выбирайте коды ошибок если:

  • Работаете в embedded системах с ограничениями
  • Требуется предсказуемое использование памяти и времени
  • Нужна максимальная производительность в critical path
  • Работаете с legacy кодом

Совет: Современный C++ (C++11+) предпочитает исключения как механизм обработки ошибок. Исключения обеспечивают лучшую безопасность и читаемость кода при правильном использовании.

В чем разница между программой с исключениями и без? | PrepBro