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

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

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

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

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

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

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

Это один из самых важных и часто упускаемых аспектов C++. При выбросе исключения в конструкторе происходит сложная цепочка событий, которые необходимо хорошо понимать для написания надёжного кода.

Основное правило

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

  • Деструктор НЕ будет вызван для этого объекта
  • Деструкторы будут вызваны только для уже успешно созданных членов класса
  • Деструкторы будут вызваны в обратном порядке инициализации
class Resource {
public:
    Resource() { std::cout << "Resource created" << std::endl; }
    ~Resource() { std::cout << "Resource destroyed" << std::endl; }
};

class MyClass {
    Resource r1;
    Resource r2;
    Resource r3;
    
public:
    MyClass() {
        std::cout << "MyClass constructor start" << std::endl;
        // r1 успешно создан
        // r2 успешно создан
        // r3 успешно создан
        throw std::runtime_error("Error in constructor!");
        std::cout << "MyClass constructor end" << std::endl;
    }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};

int main() {
    try {
        MyClass obj;
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }
    return 0;
}

Вывод:

Resource created  // r1
Resource created  // r2
Resource created  // r3
MyClass constructor start
Resource destroyed  // r3 (в обратном порядке)
Resource destroyed  // r2
Resource destroyed  // r1
Caught: Error in constructor!

Частичная инициализация

Самое важное: если исключение выброшено в теле конструктора, уже инициализированные члены будут корректно очищены через их деструкторы.

class Database {
public:
    Database() { std::cout << "DB connected" << std::endl; }
    ~Database() { std::cout << "DB disconnected" << std::endl; }
};

class File {
public:
    File() { std::cout << "File opened" << std::endl; }
    ~File() { std::cout << "File closed" << std::endl; }
};

class Application {
    Database db;  // Инициализируется первым
    File file;    // Инициализируется вторым
    
public:
    Application() {
        // После этой точки, если выброс исключения,
        // вызовут деструкторы file и db (в обратном порядке)
        throw std::runtime_error("Init failed");
    }
};

RAII паттерн и исключения

Это почему RAII (Resource Acquisition Is Initialization) так важен в C++. Каждый ресурс упаковывается в объект, чей деструктор гарантированно вызовется.

class SafeFile {
    FILE* handle;
    
public:
    SafeFile(const char* filename) {
        handle = fopen(filename, "r");
        if (!handle) {
            throw std::runtime_error("Failed to open file");
        }
    }
    
    ~SafeFile() {
        if (handle) {
            fclose(handle);  // Гарантированно освободит ресурс
        }
    }
};

class MyService {
    SafeFile file1;   // Если это выбросит исключение
    SafeFile file2;   // file1 будет корректно очищен
    
public:
    MyService() : file1("log1.txt"), file2("log2.txt") {}
};

Опасность: исключения в листе инициализации

Если исключение выброшено в списке инициализации членов (initializer list), ситуация ещё более критична:

class Example {
    int value;
    std::vector<int> vec;
    
public:
    // Если конструктор vec выбросит исключение,
    // объект Example не будет создан
    Example() : value(10), vec(1000000) {  // Может выбросить std::bad_alloc
        // Если дошли сюда, vec инициализирован
    }
};

Best Practices

1. Минимизируйте работу в конструкторе:

class Good {
public:
    Good() { /* только инициализация членов */ }
    
    void initialize() {  // throw-операции здесь
        // сложная инициализация
    }
};

2. Используйте функции инициализации (factory pattern):

std::unique_ptr<MyClass> MyClass::create(const std::string& config) {
    auto obj = std::make_unique<MyClass>();  // nothrow
    obj->initialize(config);                   // может выбросить
    return obj;
}

3. Обеспечьте сильную гарантию исключений:

  • Либо объект полностью создан, либо вообще не создан
  • Используйте RAII для всех ресурсов
  • Тестируйте сценарии сбоев

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

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