Назад к вопросам
Middle
105
questionbank

Является ли выбрасывание исключения из конструктора нормальной практикой?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Да, это стандартная и часто необходимая практика.

Если в процессе конструирования объекта возникла ошибка, которая не позволяет ему быть в корректном и полностью инициализированном состоянии, выброс исключения является единственным правильным способом сообщить об этом вызывающему коду. Объект, из которого было выброшено исключение из конструктора, не считается полностью созданным, и его деструктор не будет вызван автоматически при раскрутке стека.

Это важно потому что:

  1. Позволяет избежать создания некорректных, частично инициализированных объектов.
  2. Сигнализирует об ошибке инициализации, которую вызывающий код может обработать.
  3. Соответствует принципу RAII (Resource Acquisition Is Initialization), где успешное завершение конструктора гарантирует корректное состояние объекта и владение ресурсами.

При этом необходимо быть осторожным с ресурсами, которые были успешно аллоцированы или инициализированы до выброса исключения. Их следует освободить или откатить внутри блока try-catch самого конструктора или использовать RAII-обертки (например, интеллектуальные указатели), которые автоматически очистят ресурсы при выходе из области видимости (даже при раскрутке стека вызванной исключением).

Пример:

#include <stdexcept>
#include <new>
#include <iostream>

class MyClass {
public:
    // Конструктор, который может выбросить исключение
    MyClass(size_t size) : data_(nullptr) {
        if (size == 0) {
            // Выбрасываем исключение, если размер некорректен
            throw std::invalid_argument("Size cannot be zero");
        }
        try {
            // Пытаемся выделить память. new может выбросить std::bad_alloc
            data_ = new int[size];
            std::cout << "Memory allocated." << std::endl;
        } catch (const std::bad_alloc& e) {
            // Перебрасываем исключение после возможной обработки
            std::cerr << "Memory allocation failed: " << e.what() << std::endl;
            throw; // Перебрасываем пойманное исключение
        }
        // Если все прошло успешно, объект полностью создан
        std::cout << "Object constructed successfully." << std::endl;
    }

    // Деструктор вызывается только для полностью созданных объектов
    ~MyClass() {
        std::cout << "Destructor called." << std::endl;
        delete[] data_;
    }

private:
    int* data_;
};

int main() {
    try {
        std::cout << "Attempting to create object with size 10..." << std::endl;
        MyClass obj1(10); // Успешно создается
        // Деструктор obj1 будет вызван при выходе из try
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    std::cout << std::endl;

    try {
        std::cout << "Attempting to create object with size 0..." << std::endl;
        MyClass obj2(0); // Выбросит исключение
        // Деструктор obj2 НЕ будет вызван, так как конструктор не завершился
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}