RAII проти збирача сміття


75

Нещодавно я дивився чудову розмову Herb Sutter про "Leak Free C ++ ..." на CppCon 2016, де він розповів про використання розумних покажчиків для реалізації RAII (залучення ресурсів є ініціалізацією) - Концепції та те, як вони вирішують більшість проблем з витоками пам'яті.

Тепер мені було цікаво. Якщо я суворо дотримуюсь правил RAII, що здається непоганою справою, чому це може відрізнятися від того, що має збирач сміття в C ++? Я знаю, що з RAII програміст має повний контроль над тим, коли ресурси звільняються знову, але чи корисно це в будь-якому випадку просто мати збирач сміття? Чи справді це було б менш ефективно? Я навіть чув, що збирач сміття може бути більш ефективним, оскільки він може звільняти більші шматки пам’яті за раз, замість того, щоб звільняти невеликі фрагменти пам’яті по всьому коду.


24
Детерміноване управління ресурсами є критично важливим у всіх випадках, особливо коли ви маєте справу з некерованими ресурсами (наприклад, дескрипторами файлів, базами даних тощо). Крім того, збирання сміття завжди має певні накладні витрати, тоді як RAII не має більше накладних витрат, ніж спочатку правильне написання коду. "Вивільнення невеликих фрагментів пам'яті по всьому коду", як правило, є набагато ефективнішим, оскільки воно набагато менше руйнує роботу програми.
Коді Грей

32
Примітка: Ви говорите про ресурси , але існує більше одного виду ресурсів. Збирач сміття викликається, коли настає час звільнити пам’ять, але він не буде викликаний, коли настане час закрити файли.
Соломон Повільний

20
що-небудь краще, ніж збирання сміття
GuidoG,

11
@Veedrac Якщо ви повністю віддані RAII і використовуєте розумні вказівники скрізь, у вас також не повинно бути помилок без використання. Але навіть якщо GC (або інтелектуальні покажчики, що перераховуються повторно) врятував би вас від помилок без використання, це може маскувати сценарій, коли ви мимоволі зберігали посилання на ресурси довше, ніж ви очікували.
jamesdlin

7
@Veedrac: З Звичайно , це несправедливо. Ви дали мені дві програми для порівняння, таку, яка звільняє пам’ять, та іншу, яка ні. Щоб зробити справедливе порівняння, вам потрібно запустити реалістичне навантаження, яке насправді вимагає від GC, як ви розумієте, вступу. Не сидіти без діла. І вам потрібно мати динамічний та реалістичний шаблон розподілу пам’яті, а не FIFO чи LIFO чи якусь його варіацію. Не зовсім приголомшливо стверджувати, що програма, яка ніколи не вивільняє пам’ять, швидша, ніж та, що робить, або що купа, налаштована на вивільнення LIFO, буде швидшою, ніж та, яка цього не робить. Ну, так, звичайно, це було б.
user541686

Відповіді:


62

Якщо я суворо дотримуюсь правил RAII, що здається непоганою справою, чому це може відрізнятися від того, що має збирач сміття в C ++?

Хоча обидва мають справу з розподілом, вони роблять це абсолютно різними манерами. Якщо ви звертаєтесь до GC, подібного до Java, який додає власні накладні витрати, видаляє частину детермінізму з процесу випуску ресурсу та обробляє циклічні посилання.

Ви можете впровадити GC, однак, для окремих випадків із значно різними характеристиками продуктивності. Я застосував один раз для закриття підключень сокетів на високопродуктивному / високопропускному сервері (просто виклик API закриття сокета зайняв занадто багато часу і обмежив пропускну здатність). Це стосувалося не пам'яті, а мережевих підключень і ніякої циклічної обробки залежностей.

Я знаю, що з RAII програміст має повний контроль над тим, коли ресурси звільняються знову, але чи корисно це в будь-якому випадку просто мати збирач сміття?

Цей детермінізм є особливістю, яку GC просто не дозволяє. Іноді ви хочете мати можливість знати, що через деякий момент була проведена операція очищення (видалення тимчасового файлу, закриття мережевого з'єднання тощо).

У таких випадках GC не вирізає це, що є причиною в C # (наприклад), що у вас є IDisposableінтерфейс.

Я навіть чув, що збирач сміття може бути більш ефективним, оскільки він може звільняти більші шматки пам’яті за раз, замість того, щоб звільняти невеликі фрагменти пам’яті по всьому коду.

Може бути ... залежить від реалізації.


6
Зверніть увагу, що існують також алгоритми, які покладаються на GC і не можуть бути реалізовані за допомогою RAII. Наприклад, деякі одночасні алгоритми без блокування, де у вас є кілька потоків, що гоняться за публікацією деяких даних. Наприклад, наскільки мені відомо, немає C ++ реалізації неблокуючої хеш-карти Cliff .
Voo

3
додає, що це власні накладні витрати - ото, ви не платите витрати на мальлок і безкоштовно. Ви в основному торгуєте безкоштовним управлінням списками та підрахунком посилань для сканування живого.
the8472

5
GC в Java та .NET стосується лише звільнення пам’яті, все ще виділеної недосяжними об’єктами. Це не повністю детерміновано, однак, такі ресурси, як дескриптори файлів та мережеві з'єднання, закриваються за допомогою зовсім іншого механізму (в Java - java.io.Closeableінтерфейс та блоки "try-with-resources"), який є повністю детермінованим. Отже, частина відповіді про детермінованість «операції з очищення» є помилковою.
Rogério

3
@Voo в такому випадку ви можете стверджувати, що насправді він не є блокувальним, оскільки смітник робить блокування за вас.
user253751 02

2
@Voo Чи покладається ваш алгоритм на планувальник потоків за допомогою блокування?
user253751

38

Збір сміття вирішує певні класи ресурсних проблем, які RAII не може вирішити. В основному, це зводиться до кругових залежностей, де ви не ідентифікуєте цикл заздалегідь.

Це дає йому дві переваги. По-перше, існують певні типи проблем, які RAII не може вирішити. З мого досвіду, вони трапляються рідко.

Більший з них полягає в тому, що це дозволяє програмісту бути лінивим і не турбуватися про тривалість ресурсів пам'яті та деякі інші ресурси, на які ви не проти затримки очищення. Коли вам не потрібно дбати про певні типи проблем, ви можете більше піклуватися про інші проблеми. Це дозволяє зосередитись на частинах вашої проблеми, на яких ви хочете зосередитись.

Недоліком є ​​те, що без RAII важко керувати ресурсами, тривалість життя яких ви хочете обмежити. Мови GC в основному зводять вас до надзвичайно простих термінів життя, пов’язаних із сферою дії, або вимагають від вас керування ресурсами вручну, як у C, із зазначенням вручну, що ви закінчили з ресурсом. Їх життєва система об’єктів міцно пов’язана з ГХ і погано працює для жорсткого управління життєвими процесами великих складних (але без циклів) систем.

Чесно кажучи, управління ресурсами в C ++ вимагає великої роботи, щоб зробити це належним чином у таких великих складних (але без циклу) системах. Мови C # та подібні мови просто ускладнюють це, натомість вони полегшують простий випадок.

Більшість реалізацій GC також змушує не локальні повноцінні класи; Створення суміжних буферів загальних об'єктів або складання загальних об'єктів в один більший об'єкт - це не те, що більшість реалізацій GC спрощує. З іншого боку, C # дозволяє створювати значення типу structs з дещо обмеженими можливостями. У сучасну епоху архітектури центрального процесора зручність кеш-пам’яті є ключовим фактором, а відсутність локальних сил GC є важким тягарем. Оскільки ці мови здебільшого мають час виконання байт-коду, теоретично середовище JIT може переміщувати загальновживані дані разом, але найчастіше ви просто отримуєте рівномірну втрату продуктивності через часті помилки кешу порівняно з C ++.

Остання проблема GC полягає в тому, що вивільнення є невизначеним і іноді може спричинити проблеми з продуктивністю. Сучасні ГК роблять це меншою проблемою, ніж це було раніше.


2
Я не впевнений, що розумію вашу аргументацію щодо місцевості. Більшість сучасних GC у зрілих середовищах (Java, .Net) виконують ущільнення та створюють нові об'єкти з безперервних шматків пам'яті, призначених кожному потоку. Тому я сподіваюся, що об’єкти, створені приблизно в той самий час, будуть відносно локальними. AFAIK немає нічого подібного у стандартних mallocреалізаціях. Така логіка може призвести до помилкового обміну, що є проблемою для багатопоточного середовища, але це зовсім інша історія. У C ви можете робити явні трюки для покращення місцевості, але якщо ви цього не зробите, я очікую, що GC стане кращим. Що мені не вистачає?
SergGr

8
@SergGr Я можу створити суцільний масив об'єктів, що не є простими-старими даними в C ++, і повторювати їх по порядку. Я можу чітко пересувати їх, щоб вони знаходились поруч. Коли я перебираю суміжний контейнер значень, вони гарантовано будуть розташовані послідовно в пам'яті. Базових контейнерів вузлів не вистачає цієї гарантії, а мови gc рівномірно підтримують лише контейнери на основі вузлів (у кращому випадку у вас є суміжний буфер посилань, а не об'єктів). З деякою роботою на C ++, я навіть можу це зробити за допомогою поліморфних значень виконання (віртуальні методи тощо).
Якк - Адам Неврамон

2
Як, схоже, ти кажеш, що світ, що не є GC, дозволяє боротися за місцевість і досягати кращих результатів, ніж GC. Але це лише половина історії, оскільки за замовчуванням ви, мабуть, отримаєте гірші результати, ніж у світі GC. Насправді mallocце змушує не місцевість, з якою вам доведеться боротися, а не GC, і, отже, я думаю, що твердження у вашій відповіді, що " Більшість реалізацій GC також змушує не місцевість ", насправді не відповідає дійсності.
SergGr

2
@ Rogério Так, це те, що я називаю управлінням життєвим циклом об'єкта на основі обмеженого обсягу або C-стилю. Де ви визначаєте вручну, коли закінчується час життя вашого об’єкта, або робите це за допомогою простої справи.
Якк - Адам Неврамонт

4
Вибачте, але ні, програміст не може бути "ледачим" і "не піклуватися" про життя ресурсів пам'яті. Якщо у вас є програма, FooWidgetManagerяка керує вашими Fooоб'єктами, вона, швидше за все, зберігає зареєстровані Fooв структурі даних, що нескінченно збільшується . Такий "зареєстрований Foo" об'єкт поза межами досяжності Вашого GC, оскільки FooWidgetManagerвнутрішній список або будь-що інше містить посилання на нього . Щоб звільнити цю пам’ять, потрібно попросити FooWidgetManagerскасувати реєстрацію цього об’єкта. Якщо ви забудете, це по суті "нове без видалення"; змінилися лише імена ... і GC не може це виправити .
Х Уолтерс

14

Зверніть увагу, що RAII є ідіомою програмування, тоді як GC - це техніка управління пам'яттю. Отже, ми порівнюємо яблука з апельсинами.

Але ми можемо обмежити RAII його аспекти управління пам'яттю тільки і порівняйте це з методами ГХ.

Основна відмінність між так званими методами управління пам’яттю на основі RAII (що насправді означає підрахунок посилань , принаймні, коли ви враховуєте ресурси пам'яті та ігноруєте інші, такі як файли) та справжніми методами збору сміття є обробка кругових посилань (для циклічних графіків ) .

При підрахунку посилань вам потрібно кодувати спеціально для них (використовуючи слабкі посилання чи інші матеріали).

У багатьох корисних випадках (згадайте std::vector<std::map<std::string,int>>) підрахунок посилань є неявним (оскільки він може бути лише 0 або 1) і практично опускається, але функції конструктора та деструктора (суттєві для RAII) поводяться так, ніби є біт підрахунку посилань (який практично відсутня). В std::shared_ptrє справжній лічильник посилань. Але пам'ять все ще імпліцитно управляється вручнуnewі deleteзапускається всередині конструкторів і деструкторів), але ця "неявна"delete (у деструкторах) створює ілюзію автоматичного управління пам'яттю. Однак дзвінки newта deleteвсе одно трапляються (і вони коштують часу).

До речі, GC реалізація може (і часто робить) рукоятка округлість яким - то особливим чином, але залишити це навантаження на GC (наприклад , прочитати про алгоритмі Чейні ).

Деякі алгоритми GC (зокрема, генератор копіювання смітника поколінь) не турбуються вивільненням пам'яті для окремих об'єктів, це масове вивільнення після копіювання. На практиці Ocaml GC (або SBCL) може бути швидшим, ніж справжній стиль програмування C ++ RAII (для деяких , а не для всіх типів алгоритмів).

Деякі ГК забезпечують доопрацювання (здебільшого використовується для управління зовнішніми ресурсами, не пов'язаними з пам'яттю, такими як файли), але ви будете використовувати їх рідко (оскільки більшість значень споживають лише ресурси пам'яті). Недоліком є ​​те, що доопрацювання не дає жодної гарантії термінів. Практично кажучи, програма, яка використовує доопрацювання, використовує її як крайній засіб (наприклад, закриття файлів все одно має відбуватися більш-менш явно поза доопрацюванням, а також з ними).

Ви все ще можете мати витоки пам’яті з GC (а також з RAII, принаймні при неправильному використанні), наприклад, коли значення зберігається в якійсь змінній або якомусь полі, але ніколи не буде використано в майбутньому. Вони просто трапляються рідше.

Рекомендую прочитати довідник зі збору сміття .

У коді C ++, ви можете використовувати GC Boehm в або MPS Ravenbrook в або код самостійно трасування збирач сміття . Звичайно, використання ГК - це компроміс (є певні незручності, наприклад, невизначеність, відсутність гарантій термінів тощо).

Я не думаю, що RAII є найкращим способом роботи з пам’яттю у всіх випадках. У декількох випадках кодування вашої програми в справді та ефективно реалізованих GC (згадайте Ocaml або SBCL) може бути простішим (розроблятись) та швидшим (виконувати), ніж кодувати його у вишуканому стилі RAII на C ++ 17. В інших випадках це не так. YMMV.

Як приклад, якщо ви кодуєте інтерпретатор схеми в C ++ 17 із наймодернішим стилем RAII, вам все одно доведеться кодувати (або використовувати) явний GC всередині нього (оскільки купа схеми має циркулярність). І більшість перевірених помічників кодуються мовами, заснованими на GC, часто функціональними (єдиний, який я знаю, який кодується на C ++ - це Lean ) з поважних причин.

До речі, я зацікавлений знайти таку реалізацію схеми C ++ 17 (але менш зацікавлений у її кодуванні), бажано з деякими багатопотоковими можливостями.


16
RAII не означає підрахунок посилань, це просто std :: shared_ptr. У C ++ компілятор вставляє виклики до деструктора, коли він має доказ того, що змінні більше неможливо досягти, тобто. коли змінна виходить за межі області дії.
csiz

6
@BasileStarynkevitch більшість RAII не посилається на підрахунок, тому що підрахунок колись буде лише 1
Калет

4
RAII абсолютно не є підрахунком посилань.
Джек Ейдлі,

1
@csiz, @JackAidley, я думаю, ти неправильно зрозумів думку Базиля. Він каже, що будь-яка реалізація, схожа на підрахунок посилань (навіть настільки проста, shared_ptrщо не має явного лічильника), матиме проблеми з обробкою сценаріїв, які включають кругові посилання. Якщо ви говорите лише про прості випадки, коли ресурс використовується лише всередині одного методу, вам навіть не потрібно, shared_ptrале це лише дуже обмежений підпростір, для якого світ, заснований на GC, також використовує подібні засоби, такі як C # usingабо Java try-with-resources. Але в реальному світі існують і більш складні сценарії.
SergGr

3
@SergGr: Хто коли-небудь говорив, що unique_ptrобробляє циркулярні посилання? Ця відповідь прямо заявляє, що "так звані методи RAII" "насправді означає підрахунок посилань". Ми можемо (і я це роблю) відхилити цю претензію - і, отже, оскаржувати більшу частину цієї відповіді (як з точки зору точності, так і з точки зору відповідності) - без необхідності відхиляти кожну претензію в цій відповіді. (До речі, існують реальні збирачі сміття, які також не обробляють кругові посилання.)
ruakh 02

13

RAII та GC вирішують проблеми в абсолютно різних напрямках. Вони абсолютно різні, незважаючи на те, що хтось сказав би.

Обидва вирішують проблему, що управління ресурсами є важким. Збір сміття вирішує це, роблячи так, що розробнику не потрібно приділяти стільки уваги управлінню цими ресурсами. RAII вирішує це, полегшуючи розробникам звертати увагу на управління ресурсами. Той, хто каже, що роблять те саме, має що вам продати.

Якщо ви подивитесь на останні тенденції розвитку мов, то побачите, що обидва підходи використовуються однією мовою, бо, чесно кажучи, вам дійсно потрібні обидві сторони загадки. Ви бачите багато мов, які використовують сортування сміття, тому вам не потрібно звертати увагу на більшість об’єктів, і ці мови також пропонують рішення RAII (наприклад, withоператор python ) для часу, на який ви дійсно хочете звернути увагу. їх.

  • C ++ пропонує RAII через конструктори / деструктори та GC через shared_ptr(Якщо я можу аргументувати, що перерахунок та GC знаходяться в одному класі рішень, оскільки вони обидва розроблені, щоб допомогти вам не потрібно звертати увагу на тривалість життя)
  • Python пропонує RAII через withта GC через систему перерахунку плюс збирач сміття
  • C # пропонує RAII через IDisposableі usingта GC через генератор смітника

Візерунки з’являються на кожній мові.


10

Однією з проблем збирачів сміття є те, що важко передбачити продуктивність програми.

З RAII ви знаєте, що за точно визначений час ресурс вийде за рамки, ви очистите трохи пам'яті, і це займе деякий час. Але якщо ви не майстер налаштувань збирача сміття, ви не можете передбачити, коли відбудеться чистка.

Наприклад: очищення групи дрібних предметів може бути зроблено ефективніше за допомогою GC, оскільки це може звільнити великий шматок, але це буде не швидко, і важко передбачити, коли відбудеться, і через "очищення великих шматків" це буде займе трохи часу процесора і може вплинути на продуктивність програми


3
Я не впевнений, що можна передбачити продуктивність програми навіть за найсильніших підходів RAII. Herb Sutter дав кілька цікавих відео про те, як важливий кеш процесора і робить продуктивність напрочуд непередбачуваною.
Василь Старинкевич,

9
Кіоски @BasileStarynkevitch GC - на порядок більше, ніж пропущені кеш-пам’яті.
Dan Is Fiddling By Firelight

2
Не існує такого поняття, як "очищення великих шматків". Насправді, GC є неправильним терміном, оскільки більшість реалізацій - це "Несміттєві збирачі". Вони визначають тих, хто вижив, переносять їх в інше місце, оновлюють покажчики, а що залишається - це вільна пам’ять. Найкраще це працює, коли більшість об’єктів гине до того, як запуститься GC. Зазвичай це досить ефективно, але уникати тривалих пауз важко.
maaartinus

1
Зауважте, що паралельні збирачі сміття в режимі реального часу існують, тому передбачувана продуктивність досяжна. Хоча, як правило, "стандартний" GC для будь-якої мови розроблений для ефективності порівняно з послідовністю.
8bittree

5
Графіки об’єктів, що підраховуються за посиланнями, можуть мати дуже тривалий час звільнення, коли останній RC, що тримає графік живим, досягає нуля і всі деконструктори працюють.
the8472

9

Грубо кажучи. Ідіома RAII може бути кращою для затримки та тремтіння . Збирач сміття може бути кращим для пропускної здатності системи .


1
чому RAII страждає від пропускної здатності порівняно з GC?
LyingOnTheSky

5

"Ефективний" - це дуже широкий термін, у сенсі зусиль з розвитку RAII, як правило, менш ефективний, ніж GC, але з точки зору продуктивності GC, як правило, менш ефективний, ніж RAII. Однак можна навести додаткові приклади для обох випадків. Робота із загальним GC, коли у вас дуже чіткі шаблони розподілу ресурсів (де) у керованих мовах, може бути досить клопітною, як і код, що використовує RAII, може бути напрочуд неефективним, коли shared_ptrвикористовується для всього без причини.


5
"у сенсі зусиль з розробки RAII, як правило, менш ефективний, ніж GC" Програмувавши як на C #, так і на C ++, де ви отримуєте досить добру вибірку обох стратегій, я повинен категорично не погодитися з цим твердженням. Коли люди вважають модель RAII на C ++ менш ефективною, це більш ніж імовірно, оскільки вони використовують її неправильно. Що, строго кажучи, не є виною моделі. Найчастіше це ознака того, що люди програмують на C ++ так, ніби це Java або C #. Не важче створити тимчасовий об'єкт і дозволити його автоматично звільнити за допомогою масштабування, ніж за допомогою очікування на GC.
Коді Грей

5

Збір сміття та RAII підтримують одну загальну конструкцію, для якої інша насправді не підходить.

У системі зібраного сміття код може ефективно розглядати посилання на незмінні об'єкти (наприклад, рядки) як проксі для даних, що містяться в них; передавати такі посилання майже настільки ж дешево, як і навколо "німих" покажчиків, і швидше, ніж робити окрему копію даних для кожного власника або намагатися відстежити право власності на спільну копію даних. Крім того, системи зібраного сміття полегшують створення незмінних типів об’єктів, створюючи клас, який створює змінний об’єкт, заповнюючи його за бажанням та надаючи методи доступу, при цьому утримуючись від витоку посилань на все, що могло б змінити його, коли конструктор закінчує. У випадках, коли посилання на незмінні об'єкти потрібно широко копіювати, але самі об'єкти цього не роблять, GC б'є RAII вручає.

З іншого боку, RAII чудово справляється з ситуаціями, коли об’єкту потрібно придбати ексклюзивні послуги від сторонніх організацій. Хоча багато систем GC дозволяють об'єктам визначати методи "Доопрацювання" та вимагати сповіщення, коли вони виявляються занедбаними, і таким методам іноді вдається випускати сторонні служби, які більше не потрібні, вони рідко бувають досить надійними, щоб забезпечити задовільний спосіб забезпечення своєчасного випуску зовнішніх служб. Для управління незамінними зовнішніми ресурсами RAII перемагає GC.

Ключова різниця між випадками, коли GC виграє, порівняно з тими, де перемагає RAII, полягає в тому, що GC добре управляє взаємозамінною пам'яттю, яка може бути звільнена за необхідності, але погано справляється з незамінними ресурсами. RAII добре обробляє об'єкти з чітким правом власності, але погано обробляє безвласні незмінні власники даних, які не мають реальної ідентичності, крім даних, які вони містять.

Оскільки ні GC, ні RAII добре не обробляють всі сценарії, мовам було б корисно забезпечити хорошу підтримку для них обох. На жаль, мови, орієнтовані на одне, як правило, ставляться до іншого як до думки.


5

На основну частину питання про те, чи є той чи інший "корисним" чи більш "ефективним", неможливо відповісти, не надаючи багато контексту та не сперечаючись щодо визначень цих термінів.

Крім того, ви в основному відчуваєте напругу давнього "Чи є Java або C ++ кращою мовою?" полум’яне потріскування в коментарях. Цікаво, як може виглядати "прийнятна" відповідь на це запитання, і мені цікаво її побачити.

Але одного моменту про можливу важливу концептуальну різницю ще не вказано: з RAII ви прив'язані до нитки, яка викликає деструктор. Якщо ваша програма однопотокова (і хоча це був Герб Саттер, який заявив, що Безкоштовний обід закінчився : більшість програмних засобів сьогодні фактично все ще є однопотоковими), тоді одне ядро ​​може бути зайняте обробкою очищення об’єктів, які не є більше актуальні для фактичної програми ...

На відміну від цього, збирач сміття зазвичай працює у власному потоці або навіть у декількох потоках, і таким чином (до певної міри) відокремлений від виконання інших частин.

(Примітка: Деякі відповіді вже намагалися вказати на шаблони програм з різними характеристиками, згаданою ефективністю, продуктивністю, затримкою та пропускною здатністю, - але цей конкретний момент ще не згадувався)


4
добре, якщо ви обмежуєте навколишнє середовище, якщо ваша машина працює на одному ядрі або широко використовує багатозадачність, ваш основний, а також потоки GC обов'язково працюватимуть на одному ядрі, і повірте, перемикання контексту матиме набагато більше витрат, ніж очищення ваших ресурсів :)
Абхінав Гауніял

@AbhinavGauniyal Як я намагався підкреслити: це концептуальна різниця. Інші вже вказували на відповідальність , але зосередили це на точки зору користувача (~ " користувач відповідає за прибирання"). Моя думка полягала в тому, що це також має важливе технічне значення: чи відповідає головна програма за прибирання, чи існує (незалежна) частина інфраструктури для цього. Однак я просто подумав, що це, можливо, варто згадати, зважаючи на збільшення кількості ядер (які часто перебувають у бездіяльності в однопотокових програмах).
Marco13

так, я підтримав вас за те саме. Я просто представляв і іншу сторону вашої думки.
Абхінав Гауніял,

@ Marco13: також вартість очищення абсолютно різниться між RAII та GC. RAII у найгіршому випадку передбачає обхід через щойно звільнену складну структуру даних, враховану посиланнями. GC в гіршому випадку передбачає обхід усіх живих об'єктів, що є зворотним явищем.
ninjalj

@ninjalj Я не фахівець з деталями - збір сміття буквально є власною галуззю досліджень. Для того, щоб сперечатися щодо вартості, напевно, довелося б закріпити ключові слова до однієї конкретної реалізації (для RAII не так багато місця для різних варіантів, але принаймні я знаю, що існує досить багато реалізацій GC із набагато різними стратегіями) .
Marco13

4

RAII одноманітно має справу з усім, що можна описати як ресурс. Динамічні розподіли є одним із таких ресурсів, але вони далеко не єдиний і, можливо, не найважливіший. Файли, сокети, підключення до бази даних, зворотний зв’язок із графічним інтерфейсом та інше - все те, чим можна детерміновано керувати за допомогою RAII.

GC мають справу лише з динамічними розподілами, позбавляючи програміста від занепокоєння загальним обсягом виділених об'єктів протягом усього терміну дії програми (їм потрібно лише піклуватися про пристосування пікового паралельного обсягу розподілу)


1

RAII та збір сміття призначені для вирішення різних проблем.

Коли ви використовуєте RAII, ви залишаєте в стеці об'єкт, єдиною метою якого є очищення того, чим ви хочете керувати (сокети, пам'ять, файли тощо), виходячи зі сфери застосування методу. Це для винятку-безпеки , а не лише для збору сміття, саме тому ви отримуєте відповіді про закриття розеток та звільнення мьютексів тощо. (Гаразд, отже, ніхто, крім мене, не згадав мьютекси.) Якщо вилучено виняток, розгортання стеку природним чином очищає ресурси, використовувані методом.

Збір сміття - це програмне управління пам’яттю, хоча ви можете «збирати сміття» й іншими обмеженими ресурсами, якщо хочете. Явне звільнення їх має сенс у 99% випадків. Єдина причина використовувати RAII для чогось типу файлу чи сокета полягає в тому, що ви очікуєте, що використання ресурсу буде повним, коли метод повернеться.

Збір сміття також має справу з об’єктами, яким присвоєно купу , коли, наприклад, фабрика створює екземпляр об’єкта і повертає його. Наявність стійких об’єктів у ситуаціях, коли контроль повинен залишати область дії, - це те, що робить збір сміття привабливим. Але ви можете використовувати RAII на заводі, тому, якщо перед поверненням буде видано виняток, ви не втратите ресурси.


0

Я навіть чув, що збирач сміття може бути більш ефективним, оскільки він може звільняти більші шматки пам’яті за раз, замість того, щоб звільняти невеликі фрагменти пам’яті по всьому коду.

Це цілком здійсненно - і, насправді, насправді це зроблено - за допомогою RAII (або за допомогою звичайного malloc / free). Розумієте, вам не обов’язково завжди використовувати розподільник за замовчуванням, який вивільняє лише поштучно. У певному контексті ви використовуєте спеціальні розподільники з різними функціональними можливостями. Деякі розподільники мають вбудовану здатність звільняти все в певному регіоні розподілу, відразу, без необхідності повторювати окремі виділені елементи.

Звичайно, ви потім опиняєтесь у питанні про те, коли потрібно вивільнити все - чи потрібно використовувати ці розподільники (або плиту пам’яті, з якою вони пов’язані, RAIIed чи ні, і як.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.