При возникновении исключения в конструкторе при вызове оператора new происходит утечка памяти. Как справиться с этим?
Ответ от нейросети
sobes.tech AI
Если в конструкторе объекта, создаваемого через new, возникает исключение, память, выделенная для самого объекта с помощью operator new, не освобождается автоматически. Это приводит к утечке памяти.
Для решения этой проблемы можно применить подходы, основанные на RAII (Resource Acquisition Is Initialization):
-
"Умные" указатели (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>(); } -
Вспомогательные классы-обертки (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 также безопасно в отношении исключений, так как выделение памяти и вызов конструктора происходят атомарно.