Sobes.tech
Middle+
144
questionbank

При возникновении исключения в конструкторе при вызове оператора new происходит утечка памяти. Как справиться с этим?

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

sobes.tech AI

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

Для решения этой проблемы можно применить подходы, основанные на RAII (Resource Acquisition Is Initialization):

  1. "Умные" указатели (Smart Pointers): Использование std::unique_ptr или std::shared_ptr. Память управляется "умным" указателем, который в своем деструкторе освобождает ресурс (память), даже если конструктор объекта, на который он указывает, выбросил исключение.

    #include <memory>
    #include <stdexcept>
    
    class MyClass {
    public:
        MyClass() {
            // Имитация возникновения исключения в конструкторе
            throw std::runtime_error("Constructor exception");
        }
    };
    
    void func_with_leak_potential() {
        // Без умного указателя, память будет утекать при исключении в конструкторе
        MyClass* obj = new MyClass(); 
        // Если конструктор MyClass() выбросит исключение, этот delete не будет вызван
        delete obj; 
    }
    
    void func_with_no_leak() {
        // Используем std::unique_ptr
        // Память, выделенная operator new для MyClass, будет освобождена деструктором unique_ptr
        // даже если конструктор MyClass выбросит исключение
        std::unique_ptr<MyClass> obj = std::make_unique<MyClass>(); 
    }
    
  2. Вспомогательные классы-обертки (Helper Classes/Scopes): Создание небольших классов, задача которых — управление ресурсами в их конструкторах и деструкторах. В контексте конструктора, который может выбросить исключение, такой класс может временно владеть ресурсом (например, сырым указателем) и освободить его в своем деструкторе, если конструктор основного объекта завершился неудачно.

    #include <stdexcept>
    #include <iostream>
    
    // Вспомогательный класс для управления сырым указателем
    class PtrGuard {
    private:
        int* ptr_;
        bool released_;
    public:
        PtrGuard(int* p) : ptr_(p), released_(false) {}
        
        ~PtrGuard() {
            if (!released_ && ptr_) {
                std::cout << "Deleting ptr_ from Guard\n";
                delete ptr_;
            }
        }
        
        int* get() const { return ptr_; }
        
        void release() { released_ = true; } // Передача владения
    };
    
    class MyClassWithResource {
    private:
        int* resource_;
    public:
        MyClassWithResource() : resource_(nullptr) {
            // Выделяем ресурс
            resource_ = new int(10);
            
            // Используем Guard для управления ресурсом до полного конструирования
            PtrGuard guard(resource_); 
            
            // Имитация исключения после выделения ресурса
            // Если здесь выбросится исключение, guard.release() не будет вызвана,
            // и ресурс будет освобожден в деструкторе guard
            throw std::runtime_error("Constructor part exception");
            
            // Если исключения не было, "передаем владение" MyClassWithResource
            guard.release(); 
            // Ресурс_ теперь будет управляться деструктором MyClassWithResource
        }
        
        ~MyClassWithResource() {
            if (resource_) {
                std::cout << "Deleting resource_ from MyClassWithResource\n";
                delete resource_;
            }
        }
    };
    
    void func_with_no_leak_with_guard() {
        try {
            MyClassWithResource* obj = new MyClassWithResource();
            // Если исключение не произошло, нужно не забыть delete
            delete obj; 
        } catch (const std::runtime_error& e) {
            std::cerr << "Caught exception: " << e.what() << std::endl;
        }
    }
    

Предпочтительным и более идиоматичным в современном C++ является использование "умных" указателей (std::unique_ptr и std::shared_ptr), так как они предоставляют надежное и менее многословное решение для управления памятью и другими ресурсами в условиях исключений. Использование std::make_unique (C++14 и выше) или std::make_shared также безопасно в отношении исключений, так как выделение памяти и вызов конструктора происходят атомарно.