Middle
Ինչպե՞ս կարող է առաջանալ հիշողության արտահոսք, երբ օգտագործվում է unique_ptr։
sobes.tech AI
Պատասխան AI-ից
Гэта складана, але магчыма ў пэўных сцэнарыях:
-
Цыклічныя залежнасці (сукупнае валоданне без уліку 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 можа спрыяць уцечцы, // а не на памылках дызайну па-за яго межамі. // Самы прамы спосаб - гэта выключэнне. }