Сборка мусора в C++ - почему?

Я все время слышу, как люди жалуются, что в C++ нет сборки мусора. Я также слышал, что Комитет по стандартам C++ рассматривает возможность добавления его в язык. Боюсь, я просто не вижу в этом смысла ... использование RAII с интеллектуальными указателями устраняет необходимость в этом, верно?

Мой единственный опыт работы со сборкой мусора был на паре дешевых домашних компьютеров восьмидесятых годов, где это означало, что система время от времени зависала на несколько секунд. Я уверен, что с тех пор он улучшился, но, как вы можете догадаться, это не оставило меня высокого мнения о нем.

Какие преимущества может предложить сборка мусора опытному разработчику C++?

Вы можете описать, что такое «RAII с умными указателями»?

Craig Day 23.10.2008 09:27

Это мощная идиома C++ и хорошо известный термин в мире C++. Если вы не знаете, я бы предложил задать вопрос (или поискать, возможно, он уже есть).

coppro 23.10.2008 09:30

Он означает, что если вы строго объектно-ориентированы, вы можете полагаться на то, что delete [] будет вызываться для вашего объекта, когда он выпадает из области видимости или на него больше нет ссылок, что должно освободить () любую память и ресурсы для объект держался.

Matt J 23.10.2008 09:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
48
3
14 216
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Комитет не добавляет сборку мусора, они добавляют несколько функций, которые позволяют более безопасно реализовать сборку мусора. Только время покажет, действительно ли они повлияют на будущие компиляторы. Конкретные реализации могут сильно различаться, но, скорее всего, будут включать сбор на основе достижимости, что может привести к небольшому зависанию, в зависимости от того, как это сделано.

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

Вы правы, они не «добавляют сборку мусора». Я неправильно прочитал статью.

Head Geek 23.10.2008 10:17

«Пара функций» верна, C++ не будет использовать полагаться и не произойдет, если не используется (абстракция с нулевой стоимостью). Страуструп сказал (ссылка со страницы в Википедии) сказал My view can be summarized as "C++ is such a good garbage-collected language because it creates so little garbage that needs to be collected".

maxpolk 03.02.2016 20:44

Короткий ответ заключается в том, что сборка мусора в принципе очень похожа на RAII с интеллектуальными указателями. Если каждый фрагмент памяти, который вы когда-либо выделяли, находится внутри объекта, и на этот объект ссылаются только интеллектуальные указатели, у вас есть что-то близкое к сборке мусора (потенциально лучше). Преимущество заключается в том, что вам не нужно так рассудительно оценивать и умно указывать каждый объект, а также позволять среде выполнения делать всю работу за вас.

Этот вопрос кажется аналогом «что C++ может предложить опытному разработчику сборки? Инструкции и подпрограммы устраняют необходимость в этом, не так ли?»

Рад, что он был воспринят в том духе, в котором был задуман. Я сам склоняюсь к более ручным методам :-)

Matt J 23.10.2008 09:43

Если вы используете интеллектуальные указатели с подсчетом ссылок, остерегайтесь циклов ссылок. Одним из преимуществ сборки мусора является то, что ее не путают циклы ссылок.

CesarB 23.10.2008 16:58

Если вы правильно используете boost :: weak_ptr, циклы ссылок не будут проблемой.

Head Geek 26.10.2008 07:44

за исключением того, что интеллектуальные указатели освобождают ресурсы в тот момент, когда они выходят за пределы области видимости, сборщик мусора может заставить их оставаться на связи целую вечность. Это может иметь большое значение.

gbjbaanb 29.12.2008 00:28

@Head Geek - если вы правильно используете язык ассемблера и т.д. (см. Последний абзац ответа Мэтта Дж.).

Daniel Earwicker 04.08.2009 11:01

@gbjbaanb: «в тот момент, когда они выйдут из поля зрения». И наоборот, сборщики мусора могут собирать и собирают значения до того, как они выйдут за пределы области действия, тогда как интеллектуальные указатели с подсчетом ссылок могут сохранять значения до конца своей области действия.

J D 17.06.2013 16:21

«Если каждый фрагмент памяти, который вы когда-либо выделяли, находится внутри объекта, и на этот объект ссылаются только интеллектуальные указатели, у вас есть что-то близкое к сборке мусора (потенциально лучше)». На самом деле то, что у вас есть буквально, представляет собой форму сборки мусора, хотя и примерно в 1960 году. Наивный подсчет ссылок на основе области видимости примерно в 10 раз медленнее, чем трассирующий сборщик мусора, но в C++ вы можете вручную отказаться от некоторых вычислительных усилий, чтобы сократить разрыв в производительности . flyingfrogblog.blogspot.co.uk/2011/01/…

J D 25.06.2013 00:20

С появлением хороших средств проверки памяти, таких как valgrind, я не вижу особого смысла в сборке мусора в качестве подстраховки на случай, если мы что-то забыли освободить - тем более, что это не очень помогает в управлении более общим случаем ресурсов. кроме памяти (хотя они встречаются гораздо реже). Кроме того, явное выделение и освобождение памяти (даже с помощью интеллектуальных указателей) довольно редко встречается в коде, который я видел, поскольку контейнеры обычно намного проще и лучше.

Но сборка мусора может потенциально повысить производительность, особенно если в куче выделяется много недолговечных объектов. GC также потенциально предлагает лучшую локальность ссылок для вновь созданных объектов (сравнимых с объектами в стеке).

Грег, не могли бы вы немного расширить свой последний абзац? Мне казалось, что это задача любого распределителя памяти, даже malloc, а не только сборщиков мусора (которые, по сути, выясняют, когда вызывать free () за вас). Но я не профессионал в этом вопросе, хотелось бы более подробного объяснения.

SquareCog 23.10.2008 10:07

Одно большое потенциальное преимущество gc в производительности заключается в том, что вы можете выделить / освободить за один проход вместо множества. Это действительно зависит от ситуации: в некоторых средах ручное выделение памяти или RAII с настраиваемыми распределителями может быть проще, чем gc.

David Cournapeau 24.10.2008 14:38

У меня тоже есть сомнения, что комитет по C++ добавляет к стандарту полноценную сборку мусора.

Но я бы сказал, что основная причина добавления / использования сборки мусора на современном языке заключается в том, что существует слишком мало веских причин для сборки мусора против. С восьмидесятых годов произошло несколько значительных достижений в области управления памятью и сборки мусора, и я считаю, что существуют даже стратегии сборки мусора, которые могут дать вам гарантии, подобные программному реальному времени (например, «GC не займет больше, чем ... .. в худшем случае »).

Аргумент реального времени в любом случае спорный, потому что malloc / free также не имеют гарантии наихудшего случая.

David Cournapeau 25.10.2008 12:25

Я думаю, вы делаете взаимоисключающие предположения. Есть несколько аргументов против современных сборщиков мусора, но они требуют, чтобы скомпилированный код и сборщик мусора работали в идеальной гармонии, что практически невозможно с таким языком, как C++.

J D 17.06.2013 16:43
Ответ принят как подходящий

Я все время слышу, как люди жалуются, что в C++ нет сборки мусора.

Мне их очень жаль. Серьезно.

В C++ есть RAII, и я всегда жалуюсь, что не нахожу RAII (или кастрированный RAII) в языках со сбором мусора.

Какие преимущества может предложить сборка мусора опытному разработчику C++?

Другой инструмент.

Мэтт Дж. Написал это совершенно правильно в своем сообщении (Сборка мусора в C++ - почему?): Нам не нужны функции C++, поскольку большинство из них может быть написано на C, и нам не нужны функции C, так как большинство из них могут быть закодированы на Assembly и т. д. C++ должен развиваться.

Как разработчик: я не забочусь о GC. Я пробовал и RAII, и GC, и считаю, что RAII намного лучше. Как сказал Грег Роджерс в своем посте (Сборка мусора в C++ - почему?), утечки памяти не настолько ужасны (по крайней мере, в C++, где они редки, если C++ действительно используется), чтобы оправдать GC вместо RAII. GC имеет недетерминированное освобождение / завершение и является просто способом напишите код, который просто не заботится о конкретном выборе памяти.

Это последнее предложение важно: важно писать код, который «не волнует». Точно так же в C++ RAII мы не заботимся об освобождении ресурсов, потому что RAII делает это за нас, или об инициализации объекта, потому что конструктор делает это за нас, иногда важно просто кодировать, не заботясь о том, кто является владельцем какой памяти, и какой тип указателя (общий, слабый и т. д.) нам нужен для того или иного фрагмента кода. Похоже, что в C++ есть потребность в GC. (даже если я лично этого не вижу)

Пример хорошего использования GC в C++

Иногда в приложении есть «плавающие данные». Представьте себе древовидную структуру данных, но на самом деле никто не является «владельцем» данных (и никого не волнует, когда именно они будут уничтожены). Его могут использовать несколько объектов, а затем отказаться от него. Вы хотите, чтобы он был освобожден, когда его больше никто не использует.

Подход C++ использует умный указатель. На ум приходит boost :: shared_ptr. Таким образом, каждая часть данных принадлежит своему собственному общему указателю. Здорово. Проблема в том, что когда каждый фрагмент данных может относиться к другому фрагменту данных. Вы не можете использовать общие указатели, потому что они используют счетчик ссылок, который не поддерживает циклические ссылки (A указывает на B, а B указывает на A). Итак, вы должны много думать о том, где использовать слабые указатели (boost :: weak_ptr), а когда использовать общие указатели.

С GC вы просто используете данные с древовидной структурой.

Обратной стороной является то, что вы не должны заботиться о когда, так как «плавающие данные» действительно будут уничтожены. Только то, что его будет уничтожил.

Заключение

Итак, в конце концов, если все сделано правильно и совместимо с текущими идиомами C++, GC будет Еще один хороший инструмент для C++.

C++ - это многопарадигмальный язык: добавление GC, возможно, заставит некоторых фанатов C++ плакать из-за измены, но, в конце концов, это может быть хорошей идеей, и я думаю, что Комитет по стандартам C++ не позволит этой важной функции сломать язык, поэтому мы можем доверить им выполнение необходимой работы для включения правильного C++ GC, который не будет мешать C++: Как всегда в C++, если вам не нужна функция, не используйте ее, и она вам ничего не будет стоить.

Единственное, что я надеюсь, что мы не получим, - это объекты Phoenix (похожие на Java). Если финализатор может снова оживить объект. Но во второй раз, когда выполняется сборка мусора, финализатор не запускается.

Martin York 23.10.2008 11:35

Как я понял, C++ 09 будет сборщиком мусора способствовать.

xtofl 23.10.2008 16:31
artima.com/cppsource/cpp0x.html, статья Б. Страуструпа: «C++ 0x, скорее всего, будет поддерживать необязательную сборку мусора»
paercebal 28.10.2008 01:44

Теперь из Википедии: en.wikipedia.org/wiki/C%2B%2B0x#Transparent_garbage_collecti‌ в: «Полная поддержка сборки мусора была возвращена в более позднюю версию стандарта или Технический отчет». Думаю, ты прав. :-)

paercebal 28.10.2008 01:53

RAII C++ ограничен и может быть лучше.

Tim Matthews 09.02.2009 01:55

xtofl: Как я понял, новые стандарты C++ потребуют, чтобы C++ облегчал сборку мусора / for_memory_alone / - в C++ практически ничего не меняется, за исключением того, что память для объекта фактически не освобождается.

Arafangion 10.03.2009 07:26

@Ctrl Alt D-1337: Не могли бы вы привести нам несколько примеров «RAII C++ ограничен и может быть лучше»? Есть ли язык с лучшим RAII?

paercebal 10.06.2009 20:39

@paercebal - try / finally иногда бывает полезно (но теперь его можно смоделировать с помощью лямбда-выражений), но кроме этого RAII C++ было бы трудно улучшить.

Daniel Earwicker 04.08.2009 11:00

Более того, вам нужны как RAII, так и GC. Одно не исключает другого. Большинство языков с встроенным сборщиком мусора с самого начала также имеют идиомы, подобные RAII. И вы можете подумать, что у вас нет необходимость GC, но кто честно откажется от большего удобства и более высокой производительности, если они доступны? Pervasive GC заставляет вас проектировать и кодировать по-другому, и ваша продуктивность повышается. Еще одно преимущество, которое часто игнорируется, заключается в том, что он также работает лучше!

Daniel Earwicker 04.08.2009 11:12

@Earwicker: Основные языки с GC, которые я знаю (т. Е. Не скриптовые, не нишевые языки), - это Java и C#. В Java вообще нет RAII, а RAII в C# далек от удовлетворительного, если исходить из C++. Тем не менее, у нас есть общая точка зрения: если мы можем себе это позволить, работа на языке, в котором выделение памяти выполняется в фоновом режиме, экономит много времени.

paercebal 07.08.2009 15:03

@Earwicker: Вы правы насчет try / finally: иногда мы хотим, чтобы код выполнялся независимо от того, как мы выходим из области видимости, и писать локальную структуру только для того, чтобы ее деструктор выполнял очистку, болезненно. Другое решение - использовать Boost.ScopedExit в boost.org/doc/libs/1_39_0/libs/scope_exit/doc/html/index.htm‌ l ...

paercebal 07.08.2009 15:06

@paercebal: FWIW, деревья по определению не могут иметь циклов. Возможно, вы имели в виду «граф» над «деревом». В противном случае хороший ответ

Thomas Eding 29.12.2012 11:15

@ Томас Эдинг: Нет, я имею в виду Дерево. (XML) DOM - это структура, подобная дереву, но каждый узел имеет (обычно), помимо списка указателей на его дочерние узлы, указатель на его родительский узел (и, возможно, даже указатель на его узел документа). Это означает, что два узла всегда имеют циклическое отношение. С этим можно справиться либо с помощью классов владельцев для каждого корня (документа?), Либо с помощью комбинации shared_ptr / weak_ptr, либо с помощью GC, что означает, что вы можете удерживать часть или всю DOM, не заботясь о том, какая часть должна быть уничтожена или нет, только установив некоторые указатели на ноль ...

paercebal 29.12.2012 12:34

@Thomas Eding: ... Проблема, которая возникает с GC, конечно, заключается в том, что вы держите один крошечный узел DOM, не осознавая, что к нему прикреплено все дерево. Это своего рода утечка. И затем, если по какой-то причине какая-то скрытая часть вашего кода содержит указатель на этот крошечный узел (например, какой-то прослушиватель событий, такой как делегат в C# или анонимный внутренний класс в Java), тогда у вас действительно есть утечка, независимо от GC ... В общем, у всех видов обработки памяти есть свои проблемы ... Хорошая вещь в наличии GC в C++ была бы иметь выбор обработки памяти ... :-)

paercebal 29.12.2012 12:37

D - это язык, на котором у вас есть RAII и GC.

Quonux 05.10.2013 17:17

Python имеет GC и RAII. Python реализует RAII с диспетчерами контекста, который представляет собой профессиональный протокол, обеспечивающий детерминированное выполнение кода настройки и teardon.

Sturla Molden 31.01.2015 00:31

Более простой пример «плавающих» данных - строка. В .NET или Java передача строки не дороже, чем передача целого числа или числа с плавающей запятой, и не требует ни копирования данных, ни синхронизации памяти. Есть ли способ разработать строковый класс на C++, который был бы настолько же эффективным в многопоточных сценариях?

supercat 03.02.2015 21:03

@supercat: используя "const std :: string &"? Если вы не изменяете std :: string, передача константной ссылки в порядке, не требует ни копирования, ни синхронизации ... И если вы хотите изменить ее значение, вы генерируете другую строку на всех языках (с разными затратами) . И если вы хотите его изменить (добавить данные, изменить символ), то версия C++ будет более эффективной. В конце концов, я не уверен, что неизменяемые данные - хороший повод для оправдания сборки мусора. Я что-то пропустил?

paercebal 04.02.2015 13:12

@paercebal: Рассмотрим класс class moof { String cache; public String q(int a, String st) { if (trickyFunction(a) cache = b; else b=cache; return b;}. На языке GC этому методу никогда не нужно копировать (или делать что-либо с) содержимое какой-либо строки, и его можно использовать для строк, которые также используются в других потоках, без необходимости синхронизации памяти. Можно ли написать версию на C++ с обоими этими чертами?

supercat 04.02.2015 19:29

@paercebal: неизменяемые ссылочные типы «работают» медленнее, чем изменяемые типы, поскольку каждая операция требует создания нового объекта. С другой стороны, неизменяемые типы в системе сборки мусора могут передаваться так же дешево, как и примитивы. Во многих случаях, когда объект, однажды созданный, будет передаваться много без изменений, эффективным шаблоном является использование изменяемого объекта для создания состояния объекта, а затем создание неизменяемого объекта, инкапсулирующего это состояние. Этот шаблон заканчивается "выигрышем", если объект передается три или более раз, и может быть огромным выигрышем ...

supercat 04.02.2015 20:46

... если часто передают большой объект. Я думаю, что этот шаблон редко встречается в C++, потому что C++ не может эффективно справиться с ним, но я думаю, что в языках GC он иногда может работать намного эффективнее, чем все, что можно было бы сделать без GC.

supercat 04.02.2015 20:48

@paercebal: Я только что добавил ответ с более реалистичным примером использования.

supercat 04.02.2015 22:57

Сборка мусора действительно является основой автоматического управления ресурсами. Кроме того, сборщик мусора меняет способ решения проблем таким образом, что его трудно измерить количественно. Например, когда вы выполняете управление ресурсами вручную, вам необходимо:

  • Подумайте, когда можно освободить предмет (все ли модули / классы с ним закончили?)
  • Подумайте, кто несет ответственность за освобождение ресурса, когда он готов к освобождению (какой класс / модуль должен освобождать этот элемент?)

В тривиальном случае сложности нет. Например. вы открываете файл в начале метода и закрываете его в конце. Или вызывающий должен освободить этот возвращенный блок памяти.

Все начинает быстро усложняться, когда у вас есть несколько модулей, которые взаимодействуют с ресурсом, и не так ясно, кому нужно убирать. Конечным результатом является то, что весь подход к решению проблемы включает определенные шаблоны программирования и проектирования, которые являются компромиссом.

В языках, в которых есть сборка мусора, вы можете использовать шаблон одноразовый, где вы можете освобождать ресурсы, с которыми, как вы знаете, закончили, но если вы не можете освободить их, GC будет там, чтобы спасти положение.


Умные указатели, которые на самом деле являются прекрасным примером упомянутых мною компромиссов. Умные указатели не могут спасти вас от утечки циклических структур данных, если у вас нет механизма резервного копирования. Чтобы избежать этой проблемы, вы часто идете на компромисс и избегаете использования циклической структуры, даже если в противном случае она может быть лучше всего подходящей.

Проблема в том, что одноразовая выкройка спасет не во всех случаях. В C# шаблон «одноразового использования» сложно реализовать правильно (поскольку финализатор может вызываться несколько раз из разных потоков и т. д.), А в Java шаблон «одноразового использования» - это шутка.

paercebal 23.10.2008 10:53

И снова, правильное использование интеллектуальных указателей устраняет обе упомянутые вами проблемы.

Head Geek 23.10.2008 19:54

Правильное использование Boost :: weak_ptr также может устранить проблемы с циклическими структурами данных. Это требует полного понимания того, как работает ваш код, но вы действительно должны иметь такое понимание в любом случае.

Head Geek 25.10.2008 18:01

@Head Geek: Иногда вы просто не хотите заботиться о какой-то части вашего кода, точно так же, как вы просто не хотите заботиться о том, как std :: string выделяет / освобождает свою внутреннюю строку. Вы хотите, чтобы данные оставались там, пока вы их используете, как бы то ни было, и очищались, когда больше не используются.

paercebal 28.10.2008 02:01

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

Arafangion 10.03.2009 07:29

@HeadGeek "правильное использование умных указателей". Не существует такой вещи, как «правильное использование» интеллектуальных указателей. Рассмотрим проблему представления изменяемого графа как структуры данных и автоматического восстановления недостижимых подграфов. Не существует «правильного» способа решить эту проблему с помощью интеллектуальных указателей.

J D 17.06.2013 16:46

Сборка мусора позволяет откладывать принимать решение о том, кто является владеет объектом.

C++ использует семантику значений, поэтому с RAII действительно объекты вспоминаются при выходе из области видимости. Иногда это называют «немедленной сборкой мусора».

Когда ваша программа начинает использовать ссылочную семантику (с помощью интеллектуальных указателей и т. д.), Язык больше не поддерживает вас, вы оставлены наедине с вашей библиотекой интеллектуальных указателей.

Сложность в сборке мусора заключается в том, что когда больше не нужен.

Умные указатели полностью избавляют от необходимости решать, кому принадлежит объект.

Head Geek 23.10.2008 19:52

@Head Geek: Не совсем так. Если у вас есть 2 объекта, A и B, на которые указывают умные указатели, вы правы. Теперь, если A указывает также на B, и если B указывает также на A, тогда у вас есть проблема, и вы должны решить, кому принадлежит объект, с помощью weak_ptr и / или shared_ptr.

paercebal 28.10.2008 01:57

-1 Вы увековечиваете миф о том, что объекты становятся недоступными после того, как выходят из поля зрения.

J D 17.06.2013 16:25

@JonHarrop: Я C++, с семантикой значений, они являются недоступны, когда находятся вне области видимости. Я должен был упомянуть об этом.

xtofl 21.06.2013 14:21

Это верно, но обратное (они достижимы, если они находятся в области видимости) в целом неверно.

J D 22.06.2013 13:55

Я не понимаю, как можно утверждать, что RAII заменяет GC или намного превосходит его. Во многих случаях, обрабатываемых gc, RAII просто не может справиться вообще. Это разные звери.

Во-первых, RAII не является пуленепробиваемым: он работает против некоторых распространенных сбоев, которые широко распространены в C++, но есть много случаев, когда RAII вообще не помогает; он уязвим для асинхронных событий (например, сигналов в UNIX). По сути, RAII полагается на область видимости: когда переменная выходит за пределы области видимости, она автоматически освобождается (конечно, при условии, что деструктор реализован правильно).

Вот простой пример, в котором вам не помогут ни auto_ptr, ни RAII:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <memory>

using namespace std;

volatile sig_atomic_t got_sigint = 0;

class A {
        public:
                A() { printf("ctor\n"); };
                ~A() { printf("dtor\n"); };
};

void catch_sigint (int sig)
{
        got_sigint = 1;
}

/* Emulate expensive computation */
void do_something()
{
        sleep(3);
}

void handle_sigint()
{
        printf("Caught SIGINT\n");
        exit(EXIT_FAILURE);
}

int main (void)
{
        A a;
        auto_ptr<A> aa(new A);

        signal(SIGINT, catch_sigint);

        while (1) {
                if (got_sigint == 0) {
                        do_something();
                } else {
                        handle_sigint();
                        return -1;
                }
        }
}

Деструктор A никогда не будет вызван. Конечно, это искусственный и несколько надуманный пример, но на самом деле может случиться похожая ситуация; например, когда ваш код вызывается другим кодом, который обрабатывает SIGINT и который вы вообще не контролируете (конкретный пример: расширения mex в Matlab). По той же причине, по которой наконец, в python что-то не выполняется. Gc может вам помочь в этом случае.

Другие идиомы не подходят для этого: в любой нетривиальной программе вам понадобятся объекты с отслеживанием состояния (здесь я использую слово «объект» в очень широком смысле, это может быть любая конструкция, разрешенная языком); если вам нужно управлять состоянием вне одной функции, вы не сможете легко это сделать с RAII (вот почему RAII не так полезен для асинхронного программирования). OTOH, gc имеют представление обо всей памяти вашего процесса, то есть он знает обо всех выделенных им объектах и ​​может выполнять очистку асинхронно.

Также может быть намного быстрее использовать gc по тем же причинам: если вам нужно выделить / освободить много объектов (в частности, небольшие объекты), gc значительно превзойдет RAII, если вы не напишете собственный распределитель, поскольку gc может выделять / очистить много объектов за один проход. Некоторые хорошо известные проекты C++ используют gc даже там, где важна производительность (см., Например, Тим Суини об использовании gc в Unreal Tournament: http://lambda-the-ultimate.org/node/1277). GC в основном увеличивает пропускную способность за счет задержки.

Конечно, есть случаи, когда RAII лучше, чем gc; в частности, концепция gc в основном связана с памятью, и это не единственный ресурс. Такие вещи, как файл и т. д., Можно хорошо обрабатывать с помощью RAII. Языки без обработки памяти, такие как python или ruby, имеют что-то вроде RAII для этих случаев, BTW (с выражением в python). RAII очень полезен, когда вам нужно точно контролировать, когда ресурс освобождается, и это довольно часто бывает, например, с файлами или блокировками.

«gc видит весь ваш процесс» - 99,99 из которых НЕ являются указателем на ресурс X. Вот почему RAII хорош; он статически ограничивает количество мест, где могут скрываться соответствующие указатели. RAII также позволяет вам напрямую управлять управлением памятью - просто назначьте NULL интеллектуальному указателю.

MSalters 23.10.2008 18:28

Вы неправильно понимаете, что я имею в виду: тот факт, что gc может просматривать всю память процесса и не ограничен по объему, означает, что он может освобождать память асинхронно и освобождать несколько объектов «одновременно» (за один проход). Тот факт, что RAII статически ограничивает объем ресурсов, является не только функцией, но и проблемой.

David Cournapeau 23.10.2008 18:38

Ближе к правде сказать, что во многих случаях RAII может справиться с тем, что не может выполнить сборка мусора. GC концентрируется на памяти; RAII обрабатывает любые ресурсы. И, насколько я могу судить, умные указатели устраняют ваш аргумент о «хрупкости».

Head Geek 23.10.2008 19:50

Если вы оберните свои ресурсы в объекты, которые можно собирать, тогда GC также сможет управлять ресурсами. Если вы подкрепите это одноразовым шаблоном, вы сможете освободить ресурсы, с которыми, как вы знаете, закончили.

Luke Quinane 24.10.2008 02:21

@Head geek: например, как умный указатель обрабатывает сигналы? Что касается памяти, в большинстве случаев gc намного лучше, чем RAII. Почти каждый язык высокого уровня использует gc, я думаю, это о чем-то говорит.

David Cournapeau 24.10.2008 14:28

@Quinane: в зависимости от ресурсов вам нужно детерминированное освобождение. Как правило, для файлов (или блокировок; хотя я думаю, что RAII плохо работает и для потоков), вы хотите точно контролировать, когда вы освобождаете ресурс.

David Cournapeau 24.10.2008 14:30

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

David Cournapeau 25.10.2008 12:22

@cournape: Мне очень жаль, но этот пример кажется довольно надуманным. Вызов exit () в обработчике сигнала также не позволит сборщику мусора что-либо очистить.

Head Geek 25.10.2008 17:54

И RAII довольно хорошо работает с резьбовыми замками. Я использую его по этой причине регулярно; Фактически, именно эта ситуация познакомила меня с RAII как концепцией.

Head Geek 25.10.2008 17:55

@head geek: пример показывает, что RAII может дать сбой, и это его единственная цель. Конечно, вы никогда не стали бы использовать это в реальном коде. Но не возвращаться к вызываемому после того, как sigint происходит в реальном коде: подумайте о том, что ваш код вызывается другим кодом, который вы вообще не можете контролировать и который сам обрабатывает sigint

David Cournapeau 26.10.2008 07:14

RAII может помочь с блокировкой потоков, я согласен, но это не панацея. Думаю, меня действительно беспокоит идея, что RAII - это чудо-решение, которое волшебным образом предотвращает взаимоблокировки, утечку памяти и т. д. Это определенно полезно, но это не волшебная палочка.

David Cournapeau 26.10.2008 07:25

У RAII, без сомнения, есть свои недостатки. Но я не считаю, что сборщик мусора решает их, он просто меняет один набор недостатков на другой.

Head Geek 26.10.2008 07:47

Да, у них есть разные недостатки, это называется компромиссом :) Но gc решает проблемы, которые RAII не может решить (сохраняемость вне области видимости, как в моем примере, предполагая, что он сразу делает что-то еще, кроме захватывающего), это очень полезный инструмент. Я не уверен, что это было бы так полезно для C++.

David Cournapeau 26.10.2008 15:26

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

gbjbaanb 29.12.2008 00:32

Какие преимущества может предложить сборка мусора опытному разработчику C++?

Отсутствие необходимости выявлять утечки ресурсов в коде менее опытных коллег.

Утечки ресурсов просто не может произойти, если вы настаиваете на том, чтобы все использовали RAII и интеллектуальные указатели.

Head Geek 23.10.2008 19:53

Но установление и соблюдение этих правил имеет свою цену, и наличие их в качестве руководящих принципов не означает, что они всегда будут соблюдаться.

JohnMcG 24.10.2008 20:48

И в любом случае утечка памяти при использовании RAII и интеллектуального указателя довольно легко. Например, обработчик сигнала, который изменяет путь кода и никогда не возвращается вызываемому: ни RAII, ни интеллектуальный указатель вам в этом случае не помогут.

David Cournapeau 25.10.2008 12:12

Приобретение ресурсов - инициализация: en.wikipedia.org/wiki/Resource_acquisition_is_initialization

David Cournapeau 26.10.2008 07:28

Как бы то ни было, даже такой умный сборщик мусора, как .NET, не может предотвратить все утечки ресурсов.

FlySwat 26.10.2008 08:05

Я не считаю, что сборщик мусора помогает с ресурсами. Самая большая вещь, с которой он помогает, - это возможность использования ссылок на неизменяемые объекты в качестве прокси для их содержимого, без необходимости рассматривать содержимое как ресурс.

supercat 27.02.2015 07:24

Фактором, побуждающим к поддержке сборки мусора в C++, является лямбда-программирование, анонимные функции и т. д. Оказывается, что лямбда-библиотеки выигрывают от способности выделять память, не заботясь об очистке. Преимущество для обычных разработчиков будет заключаться в более простой, надежной и быстрой компиляции лямбда-библиотек.

GC также помогает имитировать бесконечную память; Единственная причина, по которой вам нужно удалять POD, состоит в том, что вам нужно повторно использовать память. Если у вас есть сборщик мусора или бесконечная память, больше нет необходимости удалять POD.

Другими словами, это просто костыль для неопытных программистов? :-)

Head Geek 23.10.2008 19:55

Только если рассматривать функциональное программирование как что-то для неопытных программистов, да. gc - чрезвычайно мощный инструмент, за который приходится платить: как и все мощные абстракции, он позволяет сосредоточиться на проблеме, но иногда он ломается, и вам приходится опускаться ниже абстракции.

David Cournapeau 25.10.2008 11:45

+1 за первый правильный ответ, который я видел до сих пор. Технический термин, который вы ищете, - это «проблема восходящего потока», появившийся почти полвека назад. dl.acm.org/citation.cfm?id=1093411

J D 17.06.2013 16:27

Распространенной ошибкой является предположение, что, поскольку C++ не имеет сборки мусора запеченный на языке, вы не можете использовать сборку мусора в период C++. Это нонсенс. Я знаю элитных программистов на C++, которые, естественно, используют в своей работе сборщик Boehm.

Да, я видел несколько дополнительных библиотек для сборки мусора. Я просто не понимаю, почему они необходимы или желательны в большинстве случаев. Ответы на этот вопрос дали мне (очень) несколько случаев, когда это было бы желательно, поэтому я и спросил.

Head Geek 25.10.2008 17:59

Более легкая безопасность потоков и масштабируемость

Есть одно свойство GC, которое может быть очень важным в некоторых сценариях. Назначение указателя, естественно, атомарно на большинстве платформ, в то время как создание поточно-безопасных указателей с подсчетом ссылок («умных») довольно сложно и приводит к значительным издержкам синхронизации. В результате интеллектуальным указателям часто говорят, что они «плохо масштабируются» в многоядерной архитектуре.

Это верный момент, хотя я бы обычно не беспокоился о нем. Когда я занимаюсь многопоточным программированием, потоки редко делятся своими структурами данных.

Head Geek 14.07.2009 02:53

Технически подсчет ссылок масштабируется лучше, чем другие алгоритмы сборки мусора, в асимптоте бесконечного размера кучи.

J D 17.06.2013 16:41

Сборка мусора значительно упрощает правильную и эффективную реализацию синхронизации без блокировки RCU.

На самом деле, множество структур данных и алгоритмов без блокировок и без ожидания намного проще реализовать с помощью автоматического управления памятью. Но, возможно, это бессмысленно, если распределитель не заблокирован или не ожидает, что всегда не так ...

J D 17.06.2013 16:30

@JonHarrop: только если вы предполагаете, что один распределитель используется для всех потоков, что становится узким местом. Большинство высокопроизводительных схем распределения, хотя и могут иметь блокировку, используют детализированную блокировку. И до тех пор, пока на горячем пути вашего алгоритма без блокировки / ожидания нет распределения, это в любом случае не имеет большого значения.

Ben Voigt 17.06.2013 17:25

using RAII with smart pointers eliminates the need for it, right?

Интеллектуальные указатели могут использоваться для реализации подсчета ссылок в C++, который является формой сборки мусора (автоматическое управление памятью), но производственные сборщики мусора больше не используют подсчет ссылок, поскольку у него есть некоторые важные недостатки:

  1. Циклы подсчета ссылок на утечки. Рассмотрим A↔B, оба объекта A и B ссылаются друг на друга, поэтому они оба имеют счетчик ссылок 1, и ни один из них не собирается, но оба они должны быть возвращены. Продвинутые алгоритмы, такие как пробное удаление, решают эту проблему, но добавляют много сложности. Использование weak_ptr в качестве обходного пути означает возврат к ручному управлению памятью.

  2. Наивный подсчет ссылок выполняется медленно по нескольким причинам. Во-первых, это требует частого увеличения количества ссылок вне кэша (см. Увеличить shared_ptr до 10 раз медленнее, чем сборка мусора OCaml). Во-вторых, деструкторы, внедренные в конце области видимости, могут вызывать ненужные и дорогостоящие вызовы виртуальных функций и препятствовать оптимизации, такой как устранение хвостовых вызовов.

  3. Подсчет ссылок на основе области сохраняет плавающий мусор, поскольку объекты не перерабатываются до конца области действия, тогда как сборщики мусора при трассировке могут восстанавливать их, как только они становятся недоступными, например может ли локальный объект, выделенный до цикла, быть восстановлен во время цикла?

What advantages could garbage collection offer an experienced C++ developer?

Производительность и надежность - главные преимущества. Для многих приложений ручное управление памятью требует значительных усилий программиста. Моделируя машину с бесконечной памятью, сборка мусора освобождает программиста от этого бремени, что позволяет ему сосредоточиться на решении проблем и избегать некоторых важных классов ошибок (висячие указатели, отсутствие free, двойной free). Кроме того, сборка мусора облегчает другие формы программирования, например путем решения восходящая задача Funarg (1970).

Я не думаю, что (3) проблема. Что касается цикла: да, область видимости заканчивается в конце цикла, поэтому хранилище переменных, объявленных внутри цикла, не накапливается. На практике я считаю, что управление сроком службы на основе области видимости в высшей степени практично и прекрасно работает в большинстве контекстов. И да, в некоторых случаях (восходящие сообщения, сложный многопоточный обмен данными…).

Konrad Rudolph 17.06.2013 23:45

Никакого рассмотрения unique_ptr -> сбой. Ваш пример vcalls в конце области видимости - это специфическая вещь boost :: shared_ptr, это совсем не обязательно. Кроме того, сборщики мусора хранят мусор намного больше, а не RAII. Не говоря уже о том, что GC имеет некоторые очень серьезные недостатки, такие как недетерминированное разрушение ресурсов, невозможность использования с памятью / ресурсами, отличными от GC, и т. д. Кроме того, ваш пост вводит в заблуждение, потому что он подразумевает, что пользователи RAII сталкиваются с висячими указателями , утечки памяти и двойные удаления, чего не происходит. Короче, ты плохой и неправ.

Puppy 18.06.2013 00:28

@DeadMG: "особенная вещь boost :: shared_ptr". Нет, это относится к виртуальным деструкторам. «Сборщики мусора хранят намного больше мусора, чем RAII». Я измерил требования к памяти для движка векторной графики, написанного на C++ и OCaml, и обнаружил, что C++ требует в 5 раз больше памяти, чем OCaml, собирающий мусор, из-за этой проблемы со сборкой на основе области видимости. «Пользователи RAII сталкиваются с висячими указателями, утечками памяти и двойными удалениями, чего у них нет». Использование unique_ptr, когда должно быть два владельца, может оставить висячие указатели. Ссылка подсчета утечек. Нить небезопасного shared_ptr можно дважды удалить.

J D 18.06.2013 01:29

@JonHarrop: shared_ptr не подразумевает и не требует виртуального деструктора. Использование стирания типа - выбор пользователя. Анекдот из вашей памяти - это логическая ошибка (как ни странно, анекдот). Тривиально очевидно, что у сборщика мусора всегда будет больше мусора, чем у системы, которая освобождает его в тот момент, когда на него нельзя ссылаться. Нет никаких значимых реализаций shared_ptr, не ориентированных на многопоточность, интерфейс unique_ptr не допускает двойного владения, если вы не очень стараетесь, и, честно говоря, циклы ref никогда не возникают.

Puppy 18.06.2013 02:02

«Тривиально очевидно, что у сборщика мусора всегда будет больше мусора, чем у системы, которая освобождает его в тот момент, когда на него нельзя ссылаться». Вы увековечиваете распространенный миф об управлении памятью. Подсчет ссылок на основе области действия не освобождает на самом раннем этапе. «Нет никаких значимых реализаций shared_ptr, не поддерживающих потоки». См. BOOST_SP_DISABLE_THREADS. "циклы реф. никогда не появляются". Потому что вы приносите жертвы, чтобы приспособиться к плохим стратегиям управления памятью. Циклы повсеместны на JVM и .NET.

J D 18.06.2013 13:50

В среде, поддерживающей сборщик мусора, ссылка на неизменяемый объект, например на строку, может передаваться так же, как и на примитив. Рассмотрим класс (C# или Java):

public class MaximumItemFinder
{
  String maxItemName = "";
  int maxItemValue = -2147483647 - 1;

  public void AddAnother(int itemValue, String itemName)
  {
    if (itemValue >= maxItemValue)
    {
      maxItemValue = itemValue;
      maxItemName = itemName;
    }
  }
  public String getMaxItemName() { return maxItemName; }
  public int getMaxItemValue() { return maxItemValue; }
}

Обратите внимание, что этот код никогда не должен ничего делать с содержание какой-либо из строк и может просто рассматривать их как примитивы. Оператор наподобие maxItemName = itemName;, скорее всего, сгенерирует две инструкции: загрузка регистра, за которой следует хранилище регистров. У MaximumItemFinder не будет возможности узнать, сохранят ли вызывающие AddAnother какие-либо ссылки на переданные строки, а вызывающие абоненты не смогут узнать, как долго MaximumItemFinder будет сохранять ссылки на них. Вызывающие getMaxItemName не смогут узнать, отказались ли и когда MaximumItemFinder и исходный поставщик возвращенной строки от всех ссылок на нее. Поскольку код может просто передавать строковые ссылки, как примитивные значения, ничего из этого не имеет значения.

Также обратите внимание, что хотя приведенный выше класс не будет потокобезопасным при наличии одновременных вызовов AddAnother, любой вызов GetMaxItemName гарантированно вернет действительную ссылку либо на пустую строку, либо на одну из строк, переданных в AddAnother.. Синхронизация потоков потребовалась бы, если бы кто-то хотел гарантировать какую-либо связь между именем максимального элемента и его значением, но не сохранность памяти гарантирована даже при ее отсутствии.

Я не думаю, что есть способ написать метод, подобный приведенному выше, на C++, который поддерживал бы безопасность памяти при произвольном многопоточном использовании, не используя ни синхронизацию потоков, ни требуя, чтобы каждая строковая переменная имела свою собственную копию своего содержимого. , хранится в собственном пространстве хранения, которое не может быть освобождено или перемещено в течение времени существования рассматриваемой переменной. Конечно, было бы невозможно определить тип строковой ссылки, который можно было бы определять, назначать и передавать так же дешево, как int.

Мне нравится пример. Кстати, вы уверены, что присвоение ссылок атомарно? (Примечание: после небольшого исследования это кажется таким же в C#, и я предполагаю, что это также и в Java: чтобы ваш пример работал на C++, нужно было бы также сделать присвоение указателей атомарным, либо на языке - Я так не думаю - или с помощью std :: atomic)

paercebal 05.02.2015 12:55

@paercebal: Назначение ссылок в Java и .NET так же атомарно, как и назначение int [т.е. a=b выполняет атомарное чтение a и атомарную запись b, хотя операция в целом может не быть атомарной]. Ключевое требование, которому указатели C++ не могут удовлетворить, заключается в том, что сборщик мусора должен знать о регистре, в который считывается b во время присваивания (если поток, выполняющий присваивание, отключается на некоторое время, в течение которого b перезаписывается и становится необходим цикл сборщика мусора. , регистр этого потока может быть единственной ссылкой на b где-нибудь во вселенной).

supercat 05.02.2015 19:35

@paercebal: Единственный способ увидеть, что код, подобный приведенному выше, практичен и эффективен в C++ с объектами, слишком большими для эффективного копирования, - это если у класса GCreference было бы некоторое хранилище с быстрым доступом для каждого потока; в этом сценарии a=b может транслироваться в currentThreadTempRef=b.data; a.data=currentThreadTempRef; currentThreadTempRef=null;, и если сборщик мусора сработает в любой точке этого процесса, он сможет просмотреть currentThreadTempRef каждого потока и узнать, что идентифицированные таким образом объекты должны быть закреплены.

supercat 05.02.2015 19:50

Это предполагает, что само присваивание является атомарным. Например, предположим, что один поток выполняет b = c ;, а другой одновременно выполняет a = b ;, причем b является необработанным указателем, совместно используемым двумя потоками. В этот момент b мог быть наполовину записан потоком 1, полностью прочитан потоком 2, а последний наполовину записан потоком 1 (я не предполагаю, что у нас автоматически есть атомарное присвоение указателям в стандартном переносимом C++). Это означает, что поток 2 имеет неверное значение указателя. Я прав?

paercebal 06.02.2015 19:56

@paercebal: Вы правы, что помимо предоставления формы локального хранилища потока, платформа должна также гарантировать, что запись указателя, за которой следует чтение, всегда будет видеть либо старое, либо новое значение; кроме того, если кто-то хочет использовать «обычные» хранилища, он должен иметь средство, с помощью которого GC мог бы заставить другие потоки очищать свои кеши и добавить проверку if (gcBusy) gcWait(); в процесс присвоения ссылок. Если предположить, что GC имеет эти полномочия, требование назначения указателя без разделения на сегменты является незначительным по сравнению.

supercat 06.02.2015 20:09

@paercebal: В любом случае, моя основная мысль заключается в том, что приведенный выше метод представляет собой шаблон, который является обычным, эффективным и на 100% безопасным для памяти в Java и .NET, но который было бы очень сложно поддерживать безопасным для памяти способом в C++, даже если реализация имеет доступное эффективное локальное хранилище потока [концепция, которую я хотел бы, широко поддерживалась достаточно эффективно, чтобы критичный по времени код не мог ее избежать].

supercat 06.02.2015 20:14

Другие вопросы по теме