При каких условиях в конструкторе можно выбросить exception?cplus-88

Основные условия для выбрасывания исключений в конструкторе

Конструктор может выбрасывать исключения в следующих случаях:

1. Ошибка инициализации членов класса

class ResourceHolder {
    int* resource;
public:
    ResourceHolder(size_t size) {
        resource = new int[size];  // Может бросить std::bad_alloc
        if (size > 1000) {
            throw std::runtime_error("Size too large");  // Явное исключение
        }
    }
};

2. Невозможность создать объект в валидном состоянии

class DatabaseConnection {
    ConnectionHandle handle;
public:
    DatabaseConnection() {
        handle = connect_to_database();  // Может бросить DatabaseException
        if (!handle.is_valid()) {
            throw ConnectionFailed("Cannot establish connection");
        }
    }
};

Критические правила при выбрасывании исключений

1. Гарантия безопасности ресурсов

Если конструктор бросает исключение:

  • Деструктор не будет вызван
  • Уже созданные члены-объекты будут уничтожены (в порядке, обратном инициализации)
  • Выделенная память должна быть освобождена

2. Правильная обработка исключений в списке инициализации

class SafeObject {
    std::vector<int> data;
    FileHandler file;
public:
    SafeObject(const std::string& filename)
        : data(1'000'000),  // Может бросить std::bad_alloc
          file(filename) {   // Может бросить FileException
        // Если исключение брошено здесь - деструкторы data и file будут вызваны
    }
};

Опасные ситуации

1. Утечки ресурсов при исключениях

class LeakyResource {
    int* ptr1;
    int* ptr2;
public:
    LeakyResource() {
        ptr1 = new int[100];  // Если здесь бросить исключение - утечка
        ptr2 = new int[200];  // Если здесь бросить исключение - утечка ptr1
    }
};

Решение: Использовать умные указатели или RAII-обертки

class SafeResource {
    std::unique_ptr<int[]> ptr1;
    std::unique_ptr<int[]> ptr2;
public:
    SafeResource() : ptr1(new int[100]), ptr2(new int[200]) {
        // При исключении ресурсы будут автоматически освобождены
    }
};

Идиома RAII

Лучшая практика - инициализировать ресурсы в списке инициализации:

class RAIIExample {
    std::unique_ptr<Resource> res1;
    std::unique_ptr<Resource> res2;
public:
    RAIIExample() 
        : res1(acquire_resource()),  // Если бросит исключение - res1 не создастся
          res2(acquire_resource()) { // Если бросит здесь - res1 будет уничтожен
        // Тело конструктора
    }
};

Особые случаи

  1. Конструкторы по умолчанию могут бросать исключения
  2. Конструкторы копирования/перемещения могут бросать исключения
  3. Делегирующие конструкторы (C++11) - исключение в делегирующем конструкторе

Резюмируем

Исключения в конструкторах допустимы и часто необходимы при:

  1. Ошибках инициализации ресурсов
  2. Невозможности создать валидный объект
  3. Нарушении инвариантов класса

Главные правила:

  1. Используйте RAII для управления ресурсами
  2. Инициализируйте члены в списке инициализации
  3. Гарантируйте безопасность при исключениях
  4. Документируйте возможные исключения в интерфейсе класса

Правильная обработка исключений в конструкторах - ключ к созданию надежных и безопасных C++ классов.