Чому парадигма руйнування об’єктів у зібраних сміттях мовах всебічно відсутня?


27

Шукаєте розуміння рішень навколо зібраного мовним дизайном мови. Можливо, мовний експерт міг би мене просвітити? Я родом зі С ++, тому ця сфера для мене викликає здивування.

Здається, майже всі сучасні мови, зібрані зі сміттям, підтримують об'єкти OOPy, такі як Ruby, Javascript / ES6 / ES7, Actioncript, Lua і т.д., повністю опускають парадигму деструктора / фіналізації. Python, здається, єдиний зі своїм class __del__()методом. Чому це? Чи існують функціональні / теоретичні обмеження в межах мов з автоматичним вивезенням сміття, які перешкоджають ефективній реалізації методу деструктора / доопрацювання на об'єктах?

Мені вкрай не вистачає, щоб ці мови розглядали пам'ять як єдиний ресурс, яким варто керувати. Що з розетками, ручками файлів, станами програми? Без можливості впроваджувати власну логіку для очищення ресурсів, що не належать до пам’яті, та станів щодо доопрацювання об’єкта, від мене вимагається засвоїти свою програму myObject.destroy()викликами за власним стилем, розміщуючи логіку очищення поза моїм «класом», порушуючи спробу інкапсуляції та скасовуючи мою додаток до витоків ресурсів через людські помилки, а не автоматично обробляються Gc.

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

Оновлення:

Можливо, кращий спосіб сформулювати моє запитання:

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

Я не думаю, що це випадок, коли деструктор ніколи не може зателефонувати, оскільки це було б витоком основної пам'яті, чого gcs призначений уникати. Я можу побачити можливий аргумент у тому, що деструктор / фіналізатор може не зателефонувати до певного часу в майбутньому, але це не завадило Java або Python підтримувати функціональність.

Які причини дизайну основної мови не підтримують будь-яку форму доопрацювання об'єкта?


9
Може тому, що finalize/ destroyце брехня? Немає гарантії, що вона коли-небудь буде виконана. І навіть якщо ви не знаєте, коли (дається автоматичне вивезення сміття), а за необхідності контекст все ще є (він, можливо, вже був зібраний). Тож безпечніше забезпечити послідовне стан іншими способами, і можна захотіти змусити програміста до цього.
Рафаель

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

14
Це прекрасне питання в дизайні PL, давайте його.
Андрій Бауер

3
Це насправді не є статичною / динамічною відмінністю. У багатьох статичних мовах немає фіналізаторів. Насправді, чи не мови з фіналізаторами в меншості?
Андрій Бауер

1
думаю, тут є якесь питання ... було б краще, якби ви дещо визначили терміни. java має нарешті блок, який не пов'язаний з руйнуванням об'єктів, а з виходом методу. Є й інші способи поводження з ресурсами. наприклад, у java, пул з'єднань може працювати з підключеннями, які залишаються невикористаними [x] amt часу та повертають їх. не елегантно, але це працює. Частина відповіді на ваше запитання полягає в тому, що збирання сміття - це приблизно недетермінований, не миттєвий процес і не керується об'єктами, які вже не використовуються, а обмеженнями / перекриттями пам’яті.
vzn

Відповіді:


10

Шаблон, про який ви говорите, де об’єкти знають, як очистити свої ресурси, підпадає під три відповідні категорії. Не будемо плутати деструктори з фіналізаторами - лише один пов'язаний зі збиранням сміття:

  • Моделі фіналізатор : метод очищення автоматично оголошується, визначається програмістом, викликається автоматично.

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

  • Моделі деструктора : метод очищення автоматично оголошується, визначається програмістом, викликається автоматично тільки іноді.

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

  • Схема викидання : спосіб очищення, оголошений, визначений і викликаний програмістом.

    Програмісти роблять метод утилізації і називають його самостійно - саме тут myObject.destroy()потрапляє ваш власний метод. Якщо утилізація абсолютно необхідна, тоді повинні бути викликані утилізатори на всіх можливих шляхах виконання.

Фіналізатори - це дроїди, які ви шукаєте.

Шаблон фіналізатора (шаблон, про який ви задаєте) - це механізм асоціації об'єктів із системними ресурсами (сокети, дескриптори файлів тощо) для взаємної рекультивації сміттєзбірником. Але фіналізатори в основному знаходяться на користь використовуваного алгоритму збору сміття.

Розглянемо це ваше припущення:

Мови, які пропонують автоматичне вивезення сміття ... знайте зі 100% впевненістю, коли об’єкт більше не використовується.

Технічно помилковий (спасибі, @babou). Збір сміття в основному стосується пам’яті, а не предметів. Якщо або коли алгоритм колекції реалізує, пам'ять об'єкта вже не використовується, залежить від алгоритму та (можливо) від того, як ваші об'єкти посилаються один на одного. Поговоримо про два типи сміттєзбірників. Існує маса способів змінити та доповнити їх основними методами:

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

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

TLDR

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


Ви правильні, що це не статична проти динаміка. Це проблема зі зібраними сміттями мовами. Збір сміття є складною проблемою і, ймовірно, є основною причиною, оскільки є багато кращих випадків, які слід розглянути (наприклад, що відбувається, якщо логіка finalize()викликає повторне посилання на очищений об'єкт?). Однак неможливість гарантувати виклик фіналізатора до завершення програми не зупинила Java від його підтримки. Не кажучи, що ваша відповідь невірна, можливо, неповна. Ще дуже хороший пост. Дякую.
dbcb

Дякуємо за відгук. Ось спроба завершити мою відповідь: Явно пропускаючи фіналізатори, мова змушує своїх користувачів керувати власними ресурсами. Для багатьох типів проблем це, мабуть, недолік. Особисто я віддаю перевагу вибору Java, бо маю силу фіналізаторів і ніщо не заважає мені писати та використовувати власний диспозитор. Приказка Яви: "Ей, програміст. Ти не ідіот, тож ось фіналізатор. Будь обережним".
kdbanman

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

Раді допомогти. Чи пояснення мого коментаря зрозуміло зробило мою відповідь
kdbanman

2
Це добре. Для мене справжня відповідь тут полягає в тому, що мови вирішують не реалізовувати це, оскільки сприйняте значення не переважає над проблемами реалізації функціоналу. Це не неможливо (як це демонструють Java та Python), але є багато речей, які багато мов вирішують не робити.
dbcb

5

Коротко

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

Вступ

Я припускаю, що ви запитуєте, чому мови, зібрані сміттям, не піддаються автоматичному знищенню / доопрацюванню в процесі вивезення сміття, як зазначено в зауваженні:

Мені вкрай не вистачає, щоб ці мови розглядали пам'ять як єдиний ресурс, яким варто керувати. Що з розетками, ручками файлів, станами програми?

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

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

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

Тому набагато краще покладатися на автоматичне вивезення сміття, щоб повернути ресурси. Але є два питання:

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

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

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

Довідковий підрахунок сміттєзбірників

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

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

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

Відстеження сміттєзбірників

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

Проблема полягає в тому, що ваша заява (в кінці питання):

Мови, які пропонують автоматичне вивезення сміття, здаються головними кандидатами на підтримку знищення / доопрацювання об'єктів, як вони знають зі 100% впевненістю, коли об'єкт більше не використовується.

технічно некоректно для відстеження колекторів.

Те, що відомо зі 100% впевненістю, - це те, що частини пам'яті вже не використовуються . (Точніше, слід сказати, що вони більше не доступні , оскільки деякі частини, які вже не можуть бути використані відповідно до логіки програми, як і раніше вважаються у використанні, якщо в програмі все ще є непотрібний покажчик. Дані.) Але необхідна подальша обробка та відповідні структури, щоб знати, які невикористані об'єкти можуть бути збережені в цих не використовуваних частинах пам'яті . Це неможливо визначити з того, що відомо про програму, оскільки програма більше не підключена до цих частин пам'яті.

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

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

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

Ще один момент полягає в тому, що накладні витрати на час і простір можуть стосуватися виконання програмного коду, а не лише виконання програми GC.

Я не можу дати більш точної відповіді, вказуючи на конкретні питання, тому що я не знаю специфіки багатьох мов, які ви перераховуєте. Що стосується С, типізація є дуже важким питанням, яке призводить до розвитку консервативних колекторів. Я гадаю, що це впливає і на C ++, але я не знаю C ++. Це, мабуть, підтверджує Ганс Боем, який проводив більшу частину досліджень консервативної ГК. Консервативний GC не може систематично повернути всю невикористану пам'ять саме тому, що може не вистачити точної інформації про тип даних. З тієї ж причини не вдалося б систематично застосовувати завершальні процедури.

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

1 - це абстрактне представлення колекції трасування (охоплює як копію, так і зачистку GC), все змінюється залежно від типу колектора трасування, і вивчення невикористаної частини пам’яті відрізняється, залежно від того, копія чи позначка та застосовується зачистка.


Ви даєте багато чудових деталей щодо вивезення сміття. Однак ваша відповідь насправді не погоджується з моєю - ваш конспект і мій TLDR по суті говорять те саме. І для того, що це варто, у моїй відповіді використовується приклад порахування GC як приклад, а не "сильний ухил".
kdbanman

Прочитавши більш ретельно, бачу незгоду. Я відповідно відредагую. Також моя термінологія мала бути однозначною. Питання полягало у заплутанні фіналізаторів та деструкторів та навіть згаданих дихателів на одному диханні. Варто поширити правильні слова.
kdbanman

@kdbanman Складність полягала в тому, що я звертався до вас обох, оскільки ваша відповідь стояла як орієнтир. Ви не можете використовувати підрахунок посилань як парадигматичний приклад, оскільки це слабкий GC, який рідко використовується в мовах (перевірте мови, цитовані ОП), для яких додавання фіналізаторів насправді буде простим (але з обмеженим використанням). Колекційні камери майже завжди використовуються. Але фіналізатори важко зачепитись за них, оскільки предмети, що відмирають, не відомі (всупереч твердженню, яке ви вважаєте правильним). Розмежування статичного та динамічного набору тексту не має значення, оскільки динамічне введення сховища даних є важливим.
бабу

@kdbanman Що стосується термінології, вона загалом корисна, оскільки відповідає різним ситуаціям. Але тут це не допомагає, оскільки питання полягає у передачі доопрацювання до GC. Основний GC повинен зробити лише знищення. Потрібна термінологія, яка розрізняє getting memory recycled, яку я називаю reclamation, і чистку очищення перед цим, наприклад, відновлення інших ресурсів або оновлення деяких об'єктних таблиць, які я називаю finalization. Вони здалися мені відповідними питаннями, але я, можливо, пропустив пункт, який був для мене новим термінології.
бабу

1
Спасибі @kdbanman, babou. Гарна дискусія. Я думаю, що обидва ваші пости охоплюють подібні моменти. Як ви обидва зазначаєте, основним питанням, як видається, є категорія сміттєзбиральних служб, зайнятих у мові виконання. Я знайшов цю статтю , яка очищує деякі помилкові уявлення для мене. Здається, більш надійні gcs обробляють лише необроблену пам'ять низького рівня, що робить типи об'єктів вищого рівня непрозорими для gc. Без знання внутрішніх даних пам'яті gc не може знищити об'єкти. Який, здається, ваш висновок.
dbcb

4

Шаблон об’єкта деструктора є основоположним для керування помилками в програмуванні систем, але не має нічого спільного зі збиранням сміття. Скоріше, це стосується відповідності терміну експлуатації об'єкта до обсягу, і його можна реалізувати / використовувати будь-якою мовою, яка має функції першого класу.

Приклад (псевдокод). Припустимо, у вас є "необроблений файл" типу типу типу дескриптора файлу Posix. Є чотири основних операцій, open(), close(), read(), write(). Ви хочете реалізувати "безпечний" тип файлу, який завжди очищається після себе. (Тобто, це автоматичний конструктор і деструктор.)

Я припускаю, що наша мова має обробку винятків throw, tryі finally(в мовах без виключення, ви можете налаштувати дисципліну, коли користувач вашого типу повертає спеціальне значення для вказівки на помилку.)

Ви встановлюєте функцію, яка приймає функцію, яка виконує роботу. Робоча функція приймає один аргумент (ручка до "безпечного" файлу).

with_file_opened_for_read (string:   filename,
                           function: worker_function(safe_file f)):
  raw_file rf = open(filename, O_RDONLY)
  if rf == error:
    throw File_Open_Error

  try:
    worker_function(rf)
  finally:
    close(rf)

Ви також надаєте реалізації read()і write()для safe_file(що просто викликають raw_file read()і write()). Тепер користувач використовує такий safe_fileтип:

...
with_file_opened_for_read ("myfile.txt",
                           anonymous_function(safe_file f):
                             mytext = read(f)
                             ... (including perhaps throwing an error)
                          )

Деструктор C ++ - це справді просто синтаксичний цукор для try-finallyблоку. Я майже все, що я тут робив, - це перетворити, до чого складе safe_fileклас C ++ з конструктором і деструктором. Зауважте, що C ++ не має finallyсвоїх винятків, зокрема тому, що Stroustrup вважав, що використання явного деструктора краще синтаксично (і він ввів його в мову до того, як мова мала анонімні функції).

(Це спрощення одного з способів того, як люди багато років робили помилки в мовах, схожих на Лісп. Я думаю, що вперше я натрапив на це в кінці 1980-х або на початку 1990-х, але не пам'ятаю, де.)


Це описує внутрішню структуру деструктора на основі стека в C ++, але не пояснює, чому мова, зібрана зі сміттям, не реалізує таку функціональність. Можливо, ви маєте рацію, що це не має нічого спільного зі збиранням сміття, але це пов’язано із загальним знищенням / доопрацюванням об'єктів, що, схоже, є важким або неефективним у мовах, що збираються зі сміттям. Тож якщо загальне знищення не підтримується, знищення на основі стека також видається опущеним.
dbcb

Як я вже говорив на початку: будь-яка мова, зібрана зі сміттям, яка має функції першого класу (або деяке наближення функцій першого класу), дає вам можливість надавати інтерфейси типу "кулезахисні" на зразок safe_fileта with_file_opened_for_read(об'єкт, який закривається сам, коли він виходить із сфери застосування ). Це важливо, що він не має такого ж синтаксису, як конструктори, не має значення. Lisp, Schema, Java, Scala, Go, Haskell, Rust, Javascript, Clojure підтримують достатню функцію першого класу, тому їм не потрібні деструктори для надання тієї ж корисної функції.
Мандрівна логіка

Я думаю, я бачу, що ти кажеш. Оскільки мови надають основні будівельні блоки (спробуйте / зловити / нарешті, функції першого класу тощо) для ручної реалізації функцій, подібних до деструкторів, їм не потрібні деструктори? Я міг бачити, як деякі мови рухаються цим маршрутом через простоту. Хоча, здається, малоймовірно, це головна причина для всіх перерахованих мов, але, можливо, саме так і є. Можливо, я просто в переважній меншині, яка любить деструктори C ++, і ніхто більше насправді не хвилює, що може бути причиною того, що більшість мов не застосовують деструкторів. Їм просто все одно.
dbcb

2

Це не повна відповідь на питання, але я хотів додати пару спостережень, які не були висвітлені в інших відповідях чи коментарях.

  1. Питання неявно передбачає, що ми говоримо про об'єктно-орієнтовану мову в стилі Simula, яка сама по собі обмежує. У більшості мов, навіть у тих, що мають об’єкти, не все є об’єктом. Механізм для впровадження деструкторів покладає на себе витрати, які не кожен мовний виробник готовий платити.

  2. C ++ має деякі неявні гарантії щодо порядку знищення. Якщо у вас є структура даних, що нагадує дерево, наприклад, діти будуть знищені перед батьком. Це не в мовах GC'd, тому ієрархічні ресурси можуть вивільнятися в непередбачуваному порядку. Для ресурсів, що не належать до пам'яті, це може мати значення.


2

Коли розроблялися дві найпопулярніші рамки GC (Java та .NET), я думаю, що автори очікували, що фіналізація буде працювати досить добре, щоб уникнути необхідності в інших формах управління ресурсами. Багато аспектів дизайну мови та рамок можна значно спростити, якщо немає необхідності в усіх функціях, необхідних для забезпечення 100% надійного та детермінованого управління ресурсами. У C ++ необхідно розрізняти поняття:

  1. Вказівник / посилання, що ідентифікує об'єкт, який виключно належить власнику посилання, і який не ідентифікується жодними вказівниками / посиланнями, про які власник не знає.

  2. Вказівник / посилання, що ідентифікує об'єкт, який можна поділити, яким не належить виключно ніхто.

  3. Вказівник / посилання, що ідентифікує об'єкт, який виключно належить власнику посилання, але до якого може бути доступний через "погляди", власник не може відстежувати.

  4. Вказівник / довідка, що ідентифікує об'єкт, який надає вид на об'єкт, яким належить хтось інший.

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

Мені було б наївною думка, що доопрацювання усуне потребу в інших формах управління ресурсами, але чи було таке очікування на той час обґрунтованим чи ні, історія показала, що існує чимало випадків, які потребують більш точного управління ресурсами, ніж передбачає його доопрацювання. . Я думаю, що винагорода за визнання права власності на рівні мови / рамки буде достатньою для виправдання витрат (складність повинна десь існувати, а переміщення її до мови / рамки спростить код користувача), але визнаю, що є значні переваги дизайну мати єдиний "вид" посилань - те, що працює лише в тому випадку, якщо мова / рамки є агресивними щодо питань очищення ресурсів.


2

Чому парадигма руйнування об’єктів у зібраних сміттях мовах всебічно відсутня?

Я родом зі С ++, тому ця сфера для мене викликає здивування.

Деструктор C ++ насправді поєднує дві речі . Він звільняє оперативну пам’ять і звільняє ідентифікатори ресурсів.

Інші мови відокремлюють ці проблеми, оскільки GC відповідає за звільнення оперативної пам’яті, тоді як інша мовна функція бере на себе відповідальність за звільнення ідентифікаторів ресурсів.

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

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

Що з розетками, ручками файлів, станами програми?

Мови можуть забезпечити різні способи звільнення ідентифікаторів ресурсів за допомогою:

  • посібник, .CloseOrDispose()розкиданий по коду

  • ручне .CloseOrDispose()розкидання в ручному " finallyблоці"

  • ручні «блоки ідентифікаторів ресурсів» (тобто using, with, try-с-ресурси і т.д.) , який автоматизує .CloseOrDispose()після того , як блок зроблений

  • гарантований «блоки ID ресурсу» , який автоматизує.CloseOrDispose() після того , як блок зроблений

Багато мов використовують ручні (на відміну від гарантованих) механізми, що створює можливість для керування ресурсами. Візьміть цей простий код NodeJS:

require('fs').openSync('file1.txt', 'w');
// forget to .closeSync the opened file

.. там, де програміст забув закрити відкритий файл.

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

введіть тут опис зображення

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


Здається, майже всі сучасні мови, зібрані зі сміттям, підтримують об'єкти OOPy, такі як Ruby, Javascript / ES6 / ES7, Actioncript, Lua і т.д., повністю опускають парадигму деструктора / фіналізації. Здається, Python єдиний із __del__()методом класу . Чому це?

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

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

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

Чому мова має вбудовану концепцію екземплярів об'єктів з класовими або класоподібними структурами разом із власною інстанцією (конструкторами), але повністю не оминути функціонал руйнування / завершення?

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

Я можу побачити можливий аргумент у тому, що деструктор / фіналізатор може не зателефонувати до певного часу в майбутньому, але це не завадило Java або Python підтримувати функціональність.

У Java немає деструкторів.

Документи Java згадують :

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

..але введення коду управління ресурсом-ідентифікатором всередину Object.finalizerв значній мірі розглядається як анти-шаблон ( див. ). Цей код замість цього повинен бути написаний на сайті виклику.

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

Які причини дизайну основної мови не підтримують будь-яку форму доопрацювання об'єкта?

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

Можливий випадок використання, коли ви хочете вести журнал часу між об'єктом, зібраним GC, і часом, коли більше немає жодних чітких посилань на об'єкт, як такий:

finalize() {
    Log(TimeNow() + ". Obj " + toString() + " is going to be memory-collected soon!"); // "soon"
}

-1

знайшов посилання на це в Dr Dobbs wrt c ++, який має більш загальні ідеї, які стверджують, що деструктори є проблематичними мовою, де вони реалізовані. Приблизно тут виглядає груба ідея, що головна мета деструкторів - керувати розгортанням пам'яті, а це важко виконати правильно. пам'ять розподіляється кусочно, але різні об'єкти з'єднуються, і тоді відповідальність / межі делокації не такі чіткі.

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

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

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

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


2
Цікава знахідка. Спасибі. Аргумент автора ґрунтується на виклику деструктора в неправильний час через передачу екземплярів класу за значенням, коли клас не має належного конструктора копій (що є реальною проблемою). Однак цей сценарій насправді не існує у більшості (якщо не у всіх) сучасних динамічних мовах, оскільки все передається посиланням, що дозволяє уникнути авторської ситуації. Хоча це є цікавою перспективою, я не думаю, що це пояснює, чому більшість зібраних сміттями мови вирішили опустити функцію деструктора / доопрацювання.
dbcb

2
Ця відповідь хибно представляє статтю доктора Добба: стаття не стверджує, що деструктори загалом проблематичні. У статті фактично це стверджується: примітиви управління пам’яттю схожі на goto заяви, оскільки вони є простими, але надто потужними. Таким же чином, що оператори goto найкраще інкапсульовані у "належним чином обмежених структурах управління" (Див.: Dijktsra), примітиви управління пам'яттю найкраще інкапсульовані у "відповідно обмежених структурах даних". Деструктори - це крок у цьому напрямку, але недостатньо далеко. Вирішіть самі, чи це правда чи ні.
kdbanman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.