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

Что происходит при выбрасывании исключения из конструктора?

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

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

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

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

Что происходит при выбрасывании исключения из конструктора?

Краткий ответ

Когда из конструктора выбрасывается исключение:

  1. Объект считается невалидным и не полностью инициализирован
  2. Уже выделенная память освобождается
  3. Деструктор НЕ вызывается (объект не был создан)
  4. Управление передаётся обработчику исключения

Детальный процесс

class Resource {
public:
    Resource(int id) {
        std::cout << "Конструктор начался\n";
        if (id < 0) {
            throw std::invalid_argument("ID не может быть отрицательным");
        }
        this->id = id;
        std::cout << "Конструктор завершился успешно\n";
    }
    
    ~Resource() {
        std::cout << "Деструктор вызван\n";
    }
    
private:
    int id;
};

int main() {
    try {
        Resource r(-5);  // Исключение выбросится в конструкторе
    } catch (const std::invalid_argument& e) {
        std::cout << "Поймано исключение: " << e.what() << "\n";
    }
    // Вывод:
    // Конструктор начался
    // Поймано исключение: ID не может быть отрицательным
    // Деструктор НЕ будет вызван!
    
    return 0;
}

Важный момент: Деструктор не вызывается

Это ключевое различие. Если объект не был полностью создан, деструктор не должен вызываться, потому что:

class Database {
public:
    Database(const std::string& connection_string) {
        connection = connect(connection_string);  // Может выбросить исключение
        if (!connection) {
            throw std::runtime_error("Не удалось подключиться");
        }
    }
    
    ~Database() {
        // Здесь мы предполагаем, что connection валидный
        // Если конструктор выбросит исключение, это будет UB!
        disconnect(connection);
    }
    
private:
    Connection* connection;
};

Проблема: частичная инициализация

Если конструктор выбросит исключение в середине работы, частично инициализированный объект будет просто уничтожен:

class ComplexObject {
public:
    ComplexObject() {
        resource1 = allocateResource();      // OK
        resource2 = allocateResource();      // OK
        resource3 = allocateResource();      // THROWS!
        // resource1 и resource2 утекут!
        // Их деструкторы не будут вызваны
    }
    
    ~ComplexObject() {
        // Не вызовется, потому что объект не был создан
        freeResource(resource1);
        freeResource(resource2);
        freeResource(resource3);
    }
    
private:
    Resource* resource1;
    Resource* resource2;
    Resource* resource3;
};

Решение 1: RAII (Resource Acquisition Is Initialization)

class ComplexObject {
public:
    ComplexObject() {
        resource1 = std::make_unique<Resource>();
        resource2 = std::make_unique<Resource>();
        resource3 = std::make_unique<Resource>();  // Если выбросит — предыдущие удалятся
    }
    
private:
    std::unique_ptr<Resource> resource1;
    std::unique_ptr<Resource> resource2;
    std::unique_ptr<Resource> resource3;
};

Здесь std::unique_ptr автоматически удалит предыдущие ресурсы если выбросится исключение.

Решение 2: Двухфазная инициализация

class Database {
public:
    Database() : is_initialized(false) {}
    
    void initialize(const std::string& connection_string) {
        connection = connect(connection_string);
        if (!connection) {
            throw std::runtime_error("Не удалось подключиться");
        }
        is_initialized = true;
    }
    
    ~Database() {
        if (is_initialized) {
            disconnect(connection);
        }
    }
    
private:
    Connection* connection = nullptr;
    bool is_initialized;
};

int main() {
    Database db;  // Безопасное создание
    try {
        db.initialize("localhost");  // Инициализация отделена
    } catch (...) {
        // Database остаётся в безопасном состоянии
    }
    return 0;
}

Решение 3: Factory функции

class Resource {
private:
    Resource(int id) : id(id) {}
    int id;
    
public:
    static std::unique_ptr<Resource> create(int id) {
        if (id < 0) {
            throw std::invalid_argument("ID должен быть положительным");
        }
        return std::make_unique<Resource>(id);
    }
    
    ~Resource() {
        // Деструктор только для валидных объектов
    }
};

int main() {
    try {
        auto r = Resource::create(-5);
    } catch (const std::invalid_argument& e) {
        std::cout << "Ошибка: " << e.what() << "\n";
    }
    return 0;
}

Важные моменты

  • Исключение в конструкторе базового класса также приводит к тому, что деструктор производного класса не вызовется
  • RAII правило: всегда используй умные указатели для ресурсов
  • Гарантия исключений: конструктор должен либо создать объект полностью, либо выбросить исключение
  • Не выполняй тяжёлую инициализацию в конструкторе — лучше в отдельных методах