Hogyan okozható memória szivárgás a unique_ptr használatakor?
sobes.tech MI
Válasz az MI-től
Ez nehéz, de bizonyos forgatókönyvek esetén lehetséges:
-
Ciklikus függőségek (egyedüli tulajdonlás megkerülése): Ha két
unique_ptrbirtokol olyan objektumokat, amelyek egymásra hivatkoznak, és egyik sem pusztul el elsőként, akkor a memória nem lesz felszabadítva. Ez sérti aunique_ptrmint egyedüli tulajdonos filozófiáját. Ilyen esetekben ashared_ptrjobb választásweak_ptr-el.#include <memory> struct A; struct B; struct A { std::unique_ptr<B> ptr_b; A() {} ~A() { /* A destruktor */ } }; struct B { std::unique_ptr<A> ptr_a; // Ez ciklikusan függ A-tól B() {} ~B() { /* B destruktor */ } }; int main() { auto a = std::make_unique<A>(); auto b = std::make_unique<B>(); // Kölcsönös hivatkozások beállítása // Ez memória szivárgáshoz vezet, ha a vagy b tulajdonosa nem pusztítja el elsőként a unique_ptr-t, // ami ebben az esetben a kölcsönös tulajdonítás miatt lehetetlen a->ptr_b = std::move(b); // A main kilépése után az `a` felszabadul, de destruktorában a ptr_b is próbálna felszabadulni. // Ha a ptr_b egy B objektumra mutat, amely tartalmaz egy `unique_ptr<A>`, ami az eredeti A-t tartalmazza, // ciklikus függőség alakul ki, ami szivárgáshoz vezet, ha nem kezeljük óvatosan. // Ez a példában csak az `a` marad scope-ban, és a destruktora meghívódik. // Szivárgás akkor fordul elő, ha B tartalmaz A-t, és A tartalmaz B-t. // Itt a b moved az a->ptr_b-ba. A `unique_ptr<B> b` a main-ben most üres. // Kilépéskor az `a` felszabadul, destruktora meghívódik. // A destruktorok hívódnak. // A ciklikus tulajdonlás problémája akkor jelentkezik, amikor az objektumok nem tudják egymást felszabadítani // mert mindketten tulajdonolnak valamit. // A ciklikus függőség helyes példája: // struct A { std::unique_ptr<B> b; }; // struct B { A* a_raw_ptr; }; // B tud az A-ról, de nem tulajdonolja // Unique_ptr esetén a ciklikus *tulajdonlás* rendkívül ritka és nehéz közvetlenül létrehozni // hibás tervezés nélkül. A szokásos ciklikus hivatkozás nem vezet szivárgáshoz unique_ptr esetén, // ha nincs ciklikus tulajdonítás. // **Igazi szivárgási forgatókönyv unique_ptr esetén ciklusokban:** // Ez nem a unique_ptr-ből származik, hanem a tulajdonlási logikából. // Például, ha van egy struktúra, ami *tárol* egy `unique_ptr`-t egy másik struktúrára, // ami viszont *tárol* egy `unique_ptr`-t az elsőre, és ilyen objektumokat hozol létre `new`-val, // és hozzárendeled őket `unique_ptr`-hez: // auto obj1 = std::make_unique<A>(); // auto obj2 = std::make_unique<B>(); // obj1->ptr_b = std::move(obj2); // obj1 most tulajdonolja obj2-t // obj1->ptr_b->ptr_a = std::move(obj1); // <- Itt a probléma: obj2 tulajdonolni próbálja az obj1-et, // // ami már tulajdonolni próbálja az obj2-t. // // Ez futásidejű hibához vagy nem inicializált unique_ptr-hez vezet, // // nem ciklikus szivárgáshoz, mint shared_ptr esetén. // **Legvalószínűbb szivárgási forgatókönyv unique_ptr esetén függőségekben:** // Ha nyers pointert használsz egy objektumon belül, amit `unique_ptr`-től függően tárolsz, // és ez a nyers pointer egy másik `unique_ptr` által tulajdonolt objektumra mutat, // és elfelejted felszabadítani a nyers pointert, amikor a második `unique_ptr` elpusztul *korábban*. // De ez nem a unique_ptr szivárgása, hanem a nyers pointer vagy a memória helytelen használata. // Fókuszáljunk arra, hogyan járulhat hozzá a `unique_ptr` a szivárgáshoz, // nem a rajta kívüli hibás tervezésre. // A legközvetlenebb módja ennek az, ha kivétel történik. } -
Kivétel a objektum létrehozásakor: Ha az objektum létrehozásakor, amit a
unique_ptrtulajdonol, vagy az objektum konstruktorában kivétel történik után a memória lefoglalásánál (new T()), de mielőtt a memória átadásra kerülne aunique_ptr-nek.#include <memory> class Resource { public: Resource() { // Ha itt kivétel történik... throw std::runtime_error("Hiba a Resource konstruktorában"); } ~Resource() { // Ez a destruktor nem fog meghívódni, ha kivétel történt a konstruktorban } }; int main() { Resource* res = nullptr; try { res = new Resource(); // memória lefoglalva // ... de kivétel történik, mielőtt a `unique_ptr` átveszi a tulajdonítást // std::unique_ptr<Resource> unique_resource(res); // <- ide nem jut el a kód std::cout << "Ez a sor nem fog végrehajtódni" << std::endl; } catch (const std::exception& e) { std::cerr << "Kivétel: " << e.what() << std::endl; // `res` mutatója a lefoglalt memóriára mutat, de soha nem került át a `unique_ptr`-be // és nem lett explicit felszabadítva. Szivárgás. // Helyes megoldás: delete res; // a catch blokkban } // Memória szivárgás, ha a delete res; nem lett meghívva a catch-ben. // **Hogyan lehet elkerülni a szivárgást ebben az esetben:** // Használj `std::make_unique` try { std::unique_ptr<Resource> safe_resource = std::make_unique<Resource>(); // RAII // Ha a Resource konstruktorában kivétel történik, a make_unique // helyesen kezeli a lefoglalt memória felszabadítását. } catch (const std::exception& e) { std::cerr << "Kivétel a make_unique által: " << e.what() << std::endl; // Szivárgás nincs } return 0; } -
Hibás deleter: Ha a
unique_ptrfelhasználói által definiált deletert használ, amely nem végzi el a feladatát.#include <memory> #include <iostream> struct MyData { int value; MyData(int v) : value(v) { std::cout << "MyData(" << value << ") created" << std::endl; } ~MyData() { std::cout << "MyData(" << value << ") destroyed" << std::endl; } }; // Felhasználói deleter, amely *nem* szedi le a memóriát struct NoOpDeleter { void operator()(MyData* ptr) const { std::cout << "NoOpDeleter hívódik a " << ptr->value << "-n, de nem törli!" << std::endl; // delete ptr; // <- Ezt felejtették kihagyni vagy szándékosan nem törlik } }; int main() { // Unique_ptr létrehozása felhasználói deleterrel std::unique_ptr<MyData, NoOpDeleter> data_ptr(new MyData(10)); // Kilépéskor a data_ptr meghívja a NoOpDeleter-t. // Mivel a NoOpDeleter nem hívja meg a delete-et, a `new MyData(10)` által lefoglalt memória // nem lesz felszabadítva. Szivárgás. return 0; // ~unique_ptr() meghívódik, meghívja a NoOpDeleter::operator()(data_ptr.get()), // de a memória nem törlődik. } -
**
.release()hívásadeletenélkül:** Arelease()módszer átadja a nyers pointer tulajdonjogát. Ha ezt a nyers pointert nem törlöd explicit módon (vagy nem adod át más smart pointer-nek), szivárgás történik.#include <memory> #include <iostream> struct LeakyResource { LeakyResource() { std::cout << "LeakyResource created" << std::endl; } ~LeakyResource() { std::cout << "LeakyResource destroyed" << std::endl; } }; int main() { std::unique_ptr<LeakyResource> ptr = std::make_unique<LeakyResource>(); LeakyResource* raw_ptr = ptr.release(); // ptr most már üres (nullptr) // A tulajdonjog átkerült a raw_ptr-re. // A raw_ptr által mutatott memória nem lesz automatikusan felszabadítva // a main kilépésekor. // ... munka a raw_ptr-tel ... // Elfelejtették delete-t! // delete raw_ptr; // <- Szivárgás, ha ez a sor hiányzik std::cout << "ptr is " << (ptr ? "nem null" : "null") << std::endl; // "ptr is null" fog kiíródni return 0; // a raw_ptr még mindig mutat a lefoglalt memóriára, de az nincs felszabadítva }
Minden más esetben a unique_ptr, az RAII elv alapján, helyesen kezeli a memóriát, felszabadítva azt a kilépéskor.