Sobes.tech
Middle
213

Kaip gali įvykti atminties nutekėjimas naudojant unique_ptr?

Atsakymas iš AI

sobes.tech AI

Tai yra sudėtinga, bet įmanoma tam tikrais scenarijais:

  1. Ciklinės priklausomybės (bendras valdymas, apeinant vienintelį nuosavybės modelį): Jei du unique_ptr valdo objektus, kurie vienas kitą nuorodoja, ir nei vienas jų nėra pirmas sunaikinamas, atmintis nebus atlaisvinta. Tai pažeidžia unique_ptr kaip vienintelio savininko koncepciją. Tokiais atvejais geriau naudoti shared_ptr su weak_ptr.

    #include <memory>
    
    struct A;
    struct B;
    
    struct A {
        std::unique_ptr<B> ptr_b;
        A() {}
        ~A() { /* A destruktorius */ }
    };
    
    struct B {
        std::unique_ptr<A> ptr_a; // Tai cikliškai priklauso nuo A
        B() {}
        ~B() { /* B destruktorius */ }
    };
    
    int main() {
        auto a = std::make_unique<A>();
        auto b = std::make_unique<B>();
        // Nustatome tarpusavio nuorodas
        // Tai sukels atminties nutekėjimą, jei savininkas a ar b pirmas nesunaikins savo unique_ptr,
        // kas šiuo atveju yra neįmanoma dėl ciklinės priklausomybės
        a->ptr_b = std::move(b);
        // Išėjus iš main, `a` bus sunaikintas, tačiau jo destruktorius A
        // bandys sunaikinti `ptr_b`. Tačiau, jei `ptr_b` valdo objektą B,
        // o B turi `unique_ptr<A>`, kuris valdo pradinį A objektą,
        // susidarys ciklinė priklausomybė, kuri gali sukelti nutekėjimą, jei neatsargiai valdomas.
        // Šiame supaprastintame pavyzdyje su `make_unique`, tik `a` liks scope
        // ir jo destruktorius bus iškviestas. Nutekėjimas įvyks, jei B valdo A, o A valdo B.
        // Čia `b` perkelta į `a->ptr_b`. `unique_ptr<B> b` dabar yra tuščias.
        // Išėjus iš main, `a` bus sunaikintas. Bus iškviestas destruktorius A.
        // Bus iškviestas `unique_ptr<B>` destruktorius.
        // Bus iškviestas B destruktorius.
        // Bus iškviestas `unique_ptr<A>` destruktorius B viduje.
        // Problema su cikline nuosavybe atsiranda, kai objektai negali vienas kitą sunaikinti
        // dėl to, kad kiekvienas valdo kitą.
        // Teisingas ciklinės priklausomybės pavyzdys:
        // struct A { std::unique_ptr<B> b; };
        // struct B { A* a_raw_ptr; }; // B žino apie A, bet jo nevaldo
    
        // Naudojant `unique_ptr`, ciklinė nuosavybė yra labai neįprasta ir sunkiai sukurti tiesiogiai
        // be aiškių dizaino klaidų. Paprasta ciklinė nuoroda savaime nesukelia nutekėjimo
        // su `unique_ptr`, jei nėra ciklinės nuosavybės.
    
        // **Tikras nutekėjimo scenarijus su `unique_ptr` cikliniuose priklausomybėse:**
        // Tai įvyksta ne dėl pačio `unique_ptr`, o dėl valdymo logikos.
        // Pavyzdžiui, jei turite struktūrą, kuri *laiko* `unique_ptr` į kitą struktūrą,
        // kuri savo ruožtu *laiko* `unique_ptr` į pirmą struktūrą, ir kuriate tokius objektus
        // naudodami `new` ir priskiriate juos `unique_ptr`:
        // auto obj1 = std::make_unique<A>();
        // auto obj2 = std::make_unique<B>();
        // obj1->ptr_b = std::move(obj2); // obj1 dabar valdo obj2
        // obj1->ptr_b->ptr_a = std::move(obj1); // <- ČIA PROBLEMOS: obj2 bando valdyti obj1,
        // kuris jau valdo obj2.
        // Tai sukels vykdymo klaidą arba neinicijuotą `unique_ptr`,
        // o ne ciklinį nutekėjimą kaip su `shared_ptr`.
        //
        // **Realistiškiausias nutekėjimo su `unique_ptr` scenarijus cikliniuose priklausomybėse:**
        // Jei naudojate nevaldomą rodyklę viduje vieno objekto, į kurį nurodo `unique_ptr`,
        // ir ši nevaldomoji rodyklė nurodo į objektą, kuriuo valdo kitas `unique_ptr`, ir pamirštate
        // išvalyti nevaldomą rodyklę, kai antrasis `unique_ptr` sunaikinamas anksčiau.
        // Bet tai nėra `unique_ptr` nutekėjimas, o nevaldomo rodyklės nutekėjimas arba netinkamas atminties valdymas.
    
        // Dėmesio, kaip `unique_ptr` gali prisidėti prie nutekėjimo, o ne apie dizaino klaidas už jo ribų.
        // Tarp tiesioginių būdų - išimtis.
    }