Как да се справим с изтичане на памет, когато възникне изключение в конструктора при извикване на оператора new?
sobes.tech AI
Отговор от 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 << "Изтриване на ptr_ от 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 << "Изтриване на resource_ от 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 << "Засечена изключение: " << e.what() << std::endl; } }
Предпочитаният и по-идиоматичен подход в съвременния C++ е използването на "умни" указатели (std::unique_ptr и std::shared_ptr), тъй като те предоставят надеждно и по-малко многословно решение за управление на паметта и други ресурси при изключения. Използването на std::make_unique (C++14 и нагоре) или std::make_shared също е безопасно по отношение на изключения, тъй като алокацията на памет и извикването на конструктора стават атомарно.