Sobes.tech
Middle

Ինչպե՞ս կարող է առաջանալ հիշողության արտահոսք, երբ օգտագործվում է unique_ptr։

sobes.tech AI

Պատասխան AI-ից

Гэта складана, але магчыма ў пэўных сцэнарыях:

  1. Цыклічныя залежнасці (сукупнае валоданне без уліку single-ownership): Калі два unique_ptr валодаюць аб'ектамі, якія спасылаюцца адзін на аднаго, і ні адзін з іх не знішчае іх першым, памяць не будзе вызвалена. Гэта парушае ідэалогію unique_ptr як адзінага ўладальніка. Для такіх выпадкаў лепш выкарыстоўваць shared_ptr з weak_ptr.

    #include <memory>
    
    struct A;
    struct B;
    
    struct A {
        std::unique_ptr<B> ptr_b;
        A() {}
        ~A() { /* деструктар A */ }
    };
    
    struct B {
        std::unique_ptr<A> ptr_a; // Гэта цыклічна залежыць ад A
        B() {}
        ~B() { /* деструктар B */ }
    };
    
    int main() {
        auto a = std::make_unique<A>();
        auto b = std::make_unique<B>();
        // Усталёўваем узаемныя спасылкі
        // Гэта прывядзе да ўцечкі, калі ўладальнік a або b не знішчае свой unique_ptr першым,
        // што немагчыма ў гэтым выпадку з-за цыклічнага валодання
        a->ptr_b = std::move(b);
        // Пасля выхаду з main будзе знішчаны ўнікальны ўказальнік A a, але яго деструктар A
        // паспрабуе знішчыць ptr_b. Аднак, калі ptr_b валодае аб'ектам B,
        // і аб'ект B змяшчае unique_ptr<A>, які валодаў першапачатковым аб'ектам A,
        // узнікае цыклічная залежнасць, якая прыводзіць да ўцечкі, калі не ўлічваць яе
        // асцярожна.
        // У гэтым спрошчаным прыкладзе з make_unique застанецца толькі a,
        // і яго деструктар будзе выкліканы. Уцечка адбудзецца, калі B валодае A, а A валодае B.
        // Тут b перададзены ў a->ptr_b. unique_ptr<B> b цяпер пусты.
        // Пры выхадзе з main, a будзе знішчаны. Деструктар A будзе выкліканы.
        // Деструктар unique_ptr<B> ptr_b будзе выкліканы.
        // Деструктар B будзе выкліканы.
        // Деструктар unique_ptr<A> ptr_a ў B будзе выкліканы.
        // Праблема з цыклічным валоданнем узнікае, калі аб'екты не могуць знішчыць адзін аднаго
        // з-за таго, што кожны валодае іншым.
        // Правільны прыклад цыклічнай залежнасці:
        // struct A { std::unique_ptr<B> b; };
        // struct B { A* a_raw_ptr; }; // B ведае пра A, але не валодае
    
        // У выпадку з unique_ptr цыклічнае *валоданне* вельмі нехарактэрна і цяжка стварыць непасрэдна
        // без відавочных памылак у дызайне. Звычайная цыклічная спасылка сама па сабе не прыводзіць да ўцечкі
        // з unique_ptr, калі няма цыклічнага валодання.
    
        // **Рэальны сцэнар уцечкі з unique_ptr у кантэксце цыклаў:**
        // Гэта адбываецца не з-за самага unique_ptr, а з-за логікі валодання.
        // Напрыклад, калі ў вас ёсць структура, якая *захоўвае* unique_ptr на іншую структуру,
        // якая ў сваю чаргу *захоўвае* unique_ptr на першую структуру, і вы ствараеце такія аб'екты
        // дапамогай new і перадаеце іх у unique_ptr:
        // auto obj1 = std::make_unique<A>();
        // auto obj2 = std::make_unique<B>();
        // obj1->ptr_b = std::move(obj2); // obj1 цяпер валодае obj2
        // obj1->ptr_b->ptr_a = std::move(obj1); // <- ТУТ ПРАБЛЕМА: obj2 спрабуе валодаць obj1,
        //                                      // які ўжо валодае obj2.
        //                                      // Гэта прывядзе да памылкі часу выканання або неініцыялізаванага unique_ptr,
        //                                      // а не да цыклічнай уцечкі, як з shared_ptr.
    
        // **Самы рэалістычны спосаб уцечкі з unique_ptr у кантэксце залежнасцей:**
        // Калі вы выкарыстоўваеце сыры ўказальнік унутры аднаго аб'екта, на які спасылаецца unique_ptr,
        // і гэты сыры ўказальнік спасылаецца на аб'ект, якім валодае іншы unique_ptr, і вы забываеце
        // вызваліць сыры ўказальнік, калі другі unique_ptr знікае раней.
        // Але гэта не ўцечка unique_ptr, а ўцечка сырага ўказальніка або няправільнае выкарыстанне памяці.
    
        // Засяродзімся на тым, як сам unique_ptr можа спрыяць уцечцы,
        // а не на памылках дызайну па-за яго межамі.
        // Самы прамы спосаб - гэта выключэнне.
    }