Можно ли использовать exception в конструкторе/деструкторе?cplus-29

Исключения в конструкторах

Можно ли бросать исключения?

Да, но с важными оговорками:

  1. Корректная обработка ресурсов:
    • Все выделенные до исключения ресурсы должны быть освобождены
    • Умные указатели (unique_ptr, shared_ptr) помогают автоматизировать это

Пример безопасного конструктора:

class ResourceHolder {
    std::unique_ptr<Resource> res1;
    std::unique_ptr<Resource> res2;
public:
    ResourceHolder() {
        res1 = std::make_unique<Resource>();  // Может бросить исключение
        res2 = std::make_unique<Resource>();  // Если бросит, res1 будет корректно удалён
    }
};
  1. Инициализация членов класса:
    • Исключения из списка инициализации обрабатываются автоматически
    class Example {
        std::vector<int> data;
    public:
        Example(size_t size) : data(size) {  // Если vector бросит, объект не создастся
            // Дополнительная инициализация
        }
    };
    

Паттерн "Function Try Block"

Для перехвата исключений из списка инициализации:

class DatabaseConnection {
    Connection conn;
public:
    DatabaseConnection(const std::string& params) 
    try : conn(params) {  // Блок try для списка инициализации
        // Тело конструктора
    }
    catch (const std::exception& e) {
        // Логирование ошибки
        throw;  // Повторно бросаем исключение
    }
};

Исключения в деструкторах

Можно ли бросать исключения?

Нет, если деструктор вызывается в процессе раскрутки стека (stack unwinding) при другом исключении. Это приведёт к вызову std::terminate.

Безопасные альтернативы:

  1. Логирование ошибки:

    ```MyClass() noexcept try {
        // Код, который может генерировать ошибки
    }
    catch (...) {
        logError("Destructor failed");
        // Не бросаем исключение дальше
    }
    
  2. Отдельный метод для очистки:

    class FileHandler {
        FILE* file;
    public:
        void close() {  // Явное закрытие с возможностью бросить исключение
            if (fclose(file) != 0) {
                throw FileError("Close failed");
            }
        }
        ```FileHandler() noexcept {
            try { close(); }
            catch (...) { /* логирование */ }
        }
    };
    
  3. Использование std::uncaught_exceptions() (C++17):

    ```MyClass() noexcept(false) {  // Только если уверены в безопасности
        if (std::uncaught_exceptions() == 0) {
            // Можем бросать исключение, если нет активного исключения
        }
    }
    

Правила безопасности

  1. Конструкторы:

    • Разрешается бросать исключения
    • Должны освобождать все выделенные ресурсы
    • Лучше использовать RAII-обёртки
  2. Деструкторы:

    • Должны быть помечены noexcept (неявно или явно)
    • Не должны бросать исключения при stack unwinding
    • Все ошибки должны обрабатываться внутри деструктора

Примеры проблемных случаев

Двойное исключение

class Problematic {
public:
    ```Problematic() {
        throw std::runtime_error("Error");  // Вызовет terminate(), если есть активное исключение
    }
};

void test() {
    try {
        Problematic p;
        throw std::runtime_error("First error");
    } catch (...) { /* Не будет вызван */ }
}

Утечка ресурсов

class Leaky {
    int* resource;
public:
    Leaky() : resource(new int(42)) {
        throw std::runtime_error("Oops");  // Утечка памяти!
    }
    ```Leaky() { delete resource; }
};

Лучшие практики

  1. Для конструкторов:

    • Используйте RAII для всех ресурсов
    • Документируйте возможные исключения
    • Разделяйте сложную инициализацию на этапы
  2. Для деструкторов:

    • Избегайте операций, которые могут бросить исключение
    • Если обработка ошибок критична, предоставьте явный метод close()
    • Логируйте ошибки, но не позволяйте им покидать деструктор

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