Почему в C ++ нет сборщика мусора?


270

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

С учетом сказанного, почему он не был добавлен? Уже есть несколько сборщиков мусора для C ++. Это просто одна из тех вещей, которые легче сказать, чем сделать? Или есть другие причины, по которым он не был добавлен (и не будет добавлен в C ++ 11)?

Перекрестные ссылки:

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


26
Это один из десяти лучших мифов о C ++, которые всегда воспитывают ненавистники. Сборка мусора не «встроена», но есть несколько простых способов сделать это C ++. Публикация комментария, потому что другие уже ответили лучше, чем я мог ниже :)
davr

5
Но в этом и заключается смысл того, чтобы не быть встроенным, вы должны сделать это сами. Реальность от высокого до низкого: встроенный, библиотека, самодельный. Я использую C ++ и определенно не ненавистник, потому что это лучший язык в мире. Но динамическое управление памятью - это боль.
QBziZ

4
@Davr - я не ненавистник C ++ и даже не пытаюсь даже утверждать, что C ++ нуждается в сборщике мусора. Я спрашиваю, потому что я знаю, что Бьярн Страуструп сказал, что он будет добавлен, и ему было просто любопытно, каковы причины его неисполнения.
Джейсон Бейкер

1
В этой статье Boehm Collector for C и C ++ от Dr. Dobbs описывается сборщик мусора с открытым исходным кодом, который можно использовать как с C, так и с C ++. В нем рассматриваются некоторые проблемы, возникающие при использовании сборщика мусора с деструкторами C ++, а также стандартной библиотеки C.
Ричард Чемберс

1
@rogerdpack: Но сейчас это не так полезно (см. мой ответ ...), поэтому вряд ли реализация будет инвестировать в его создание.
einpoklum

Ответы:


160

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

Цитата самого Бьярна Страуструпа:

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

Существует хорошее обсуждение этой темы здесь .

Общий обзор:

C ++ очень мощный и позволяет вам делать практически все что угодно. По этой причине он не навязывает вам автоматически много вещей, которые могут повлиять на производительность. Сборка мусора может быть легко реализована с помощью интеллектуальных указателей (объектов, которые обертывают указатели счетчиком ссылок, которые автоматически удаляются, когда счетчик ссылок достигает 0).

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

Есть 2 типа сборки мусора ...

Явная сборка мусора:

C ++ 0x будет иметь сборку мусора через указатели, созданные с помощью shared_ptr

Если вы хотите, вы можете использовать его, если вы не хотите, вы не обязаны использовать его.

В настоящее время вы можете использовать boost: shared_ptr, если вы не хотите ждать C ++ 0x.

Неявная сборка мусора:

У этого нет прозрачной сборки мусора все же. Это будет основной темой для будущих спецификаций C ++.

Почему Tr1 не имеет неявной сборки мусора?

Бьярн Страуструп (Bjarne Stroustrup) в предыдущих интервью сказал, что есть много вещей, которые должен был иметь tr1 в C ++ 0x.


71
Я бы стал ненавистником, если бы C ++ навязал мне сборку мусора! Почему люди не могут использовать smart_ptr's? Как бы вы сделали низкоуровневую разметку в стиле Unix, с мешающим сборщиком мусора? Другие вещи будут затронуты, такие как многопоточность. Python имеет глобальную блокировку интерпретатора в основном из-за его сборки мусора (см. Cython). Держите это подальше от C / C ++, спасибо.
unixman83

26
@ unixman83: Основная проблема со сборщиком мусора с подсчетом ссылок (т.е. std::shared_ptr) - это циклические ссылки, которые вызывают утечку памяти. Поэтому вы должны осторожно использовать std::weak_ptrдля прерывания циклов, что является грязным. Марка и развертка в стиле GC не имеют этой проблемы. Нет никакой внутренней несовместимости между многопоточностью / разветвлением и сборкой мусора. Java и C # имеют высокопроизводительную вытесняющую многопоточность и сборщик мусора. Существуют проблемы, связанные с приложениями реального времени и сборщиком мусора, поскольку большинству сборщиков мусора приходится мешать миру работать.
Андрей Томазос

9
«Основная проблема со сборкой мусора (то есть с подсчетом ссылок std::shared_ptr) - циклические ссылки» и ужасная производительность, которая иронична, потому что повышение производительности обычно оправдывает использование C ++ ... flyingfrogblog.blogspot.co.uk/2011/01/…
Джон Harrop

11
Msgstr "Как бы вы делали разветвление в стиле Unix на низком уровне". Так же, как языки GC, такие как OCaml, делают это в течение ~ 20 лет или более.
Джон Харроп

9
«Python имеет глобальную блокировку интерпретатора в основном из-за его сборки мусора». Strawman аргумент. Java и .NET имеют GC, но не имеют глобальных блокировок.
Джон Харроп

149

Чтобы добавить к дискуссии здесь.

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

1. Производительность?

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

В настоящее время широко распространены 2 семейства GC:

  • Марка-и-Sweep вид
  • Подсчет ссылок

Это Mark And Sweepбыстрее (меньше влияет на общую производительность), но страдает от синдрома «заморозить мир»: то есть, когда включается GC, все остальное останавливается, пока GC не выполнит очистку. Если вы хотите построить сервер, который отвечает за несколько миллисекунд ... некоторые транзакции не будут соответствовать вашим ожиданиям :)

Проблема в Reference Countingдругом: подсчет ссылок увеличивает издержки, особенно в многопоточных средах, потому что вам нужен атомный счет. Кроме того, существует проблема референтных циклов, поэтому вам нужен умный алгоритм для обнаружения этих циклов и их устранения (обычно реализуется также «заморозить мир», хотя и реже). В целом, на сегодняшний день этот тип (хотя обычно более отзывчивый или, скорее, замерзает реже) медленнее, чем Mark And Sweep.

Я видел статью разработчиков Eiffel, которые пытались реализовать Reference Countingсборщик мусора, который имел бы аналогичную глобальную производительность Mark And Sweepбез аспекта «Freeze The World». Требуется отдельная тема для GC (типовая). Алгоритм был немного пугающим (в конце), но статья хорошо поработала, представляя концепции по одному и показывая эволюцию алгоритма от «простой» версии к полноценной. Рекомендуем прочитать, если только я смогу вернуть руки к PDF-файлу ...

2. Приобретение ресурсов - это инициализация (RAII)

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

  • блокировки (многопоточность, дескриптор файла, ...)
  • соединения (с базой данных, другим сервером, ...)

Идея состоит в том, чтобы должным образом контролировать время жизни объекта:

  • он должен быть живым столько, сколько вам нужно
  • это должно быть убито, когда вы закончите с этим

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

У языков с GC есть два обходных пути:

  • не используйте GC, когда выделение стека достаточно: обычно это связано с проблемами производительности, но в нашем случае это действительно помогает, поскольку область действия определяет время жизни
  • usingпостроить ... но это явный (слабый) RAII, в то время как в C ++ RAII является неявным, так что пользователь НЕ МОЖЕТ невольно сделать ошибку (опуская usingключевое слово)

3. Умные указатели

Умные указатели часто выглядят как серебряная пуля для обработки памяти C++. Часто я слышал: нам не нужен GC, потому что у нас есть умные указатели.

Нельзя быть более неправильным.

Умные указатели действительно помогают: auto_ptrи unique_ptrиспользуют концепции RAII, что чрезвычайно полезно. Они настолько просты, что вы можете написать их самостоятельно довольно легко.

Однако, когда нужно разделить владение, это становится более сложным: вы можете делиться между несколькими потоками, и есть несколько тонких проблем с обработкой счетчика. Поэтому человек естественно идет навстречу shared_ptr.

Это здорово, для этого все-таки Boost, но это не серебряная пуля. На самом деле, главная проблема в shared_ptrтом, что он эмулирует GC, реализованный с помощью, Reference Countingно вам нужно реализовать обнаружение цикла самостоятельно ... Urg

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

4. Какое решение?

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

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

Так что, действительно, было бы здорово иметь GC ... однако это не тривиальная проблема. А пока нам просто нужно закатать рукава.


2
Я хотел бы принять два ответа! Это просто замечательно. В отношении производительности следует отметить, что сборщик мусора, работающий в отдельном потоке, довольно распространен (он используется в Java и .Net). Конечно, это может быть неприемлемо во встроенных системах.
Джейсон Бейкер

14
Только два типа? Как насчет копирования коллекционеров? Поколение коллекционеров? Ассорти из параллельных коллекторов (включая жесткую беговую дорожку Бейкера в реальном времени)? Различные гибридные коллекционеры? Чувак, полное невежество в индустрии этой области меня иногда удивляет.
ПРОСТО МОЕ правильное мнение

12
Я говорил, что было только 2 типа? Я сказал, что было 2, которые были широко развернуты. Насколько я знаю, Python, Java и C # теперь используют алгоритмы Mark и Sweep (Java раньше имел алгоритм подсчета ссылок). Чтобы быть еще более точным, мне кажется, что C # использует Generational GC для малых циклов, Mark And Sweep для больших циклов и Copying для борьбы с фрагментацией памяти; хотя я бы сказал, что сердце алгоритма - это Mark And Sweep. Знаете ли вы какой-нибудь основной язык, который использует другую технологию? Я всегда рад учиться.
Матье М.

3
Вы только что назвали один основной язык, который использовал три.
Просто мое правильное мнение

3
Основное различие заключается в том, что поколению и инкрементальному сборщику мусора не нужно останавливать работу мира, и вы можете заставить их работать в однопоточных системах без особых накладных расходов, периодически выполняя итерации обхода дерева при обращении к указателям сборщика мусора (фактор может быть определено по количеству новых узлов, наряду с базовым прогнозом необходимости сбора). Вы можете продвинуть GC еще дальше, включив данные о том, где в коде происходило создание / изменение узла, что может помочь вам улучшить ваши прогнозы, и вы получите Escape-анализ бесплатно с ним.
Келдон Аллейн

56

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

C / c ++ используется в самых разных обстоятельствах. Я подозреваю, что что-то вроде повышения умных указателей будет достаточно для большинства пользователей

Редактировать - автоматические сборщики мусора - это не столько проблема производительности (вы всегда можете купить больше серверов), это вопрос предсказуемой производительности.
Не знать, когда GC собирается вступить в бой, это все равно, что нанимать пилота-нарколептера, в большинстве случаев они великолепны, но когда вам действительно нужна отзывчивость!


6
Я определенно понимаю вашу точку зрения, но я вынужден спросить: разве Java не используется почти во многих приложениях?
Джейсон Бейкер

35
Нет. Java не подходит для высокопроизводительных приложений по той простой причине, что она не имеет гарантий производительности в той же степени, что и C ++. Таким образом, вы найдете его в мобильном телефоне, но вы не найдете его в коммутаторе или суперкомпьютере.
Затрус

11
Вы всегда можете купить больше сервера, но вы не всегда можете купить больше процессора для мобильного телефона, уже находящегося в кармане клиента!
Crashworks

8
Java сильно повлияла на производительность процессора. Действительно неразрешимой проблемой является использование памяти, Java по своей природе менее эффективна, чем C ++. И эта неэффективность связана с тем, что это мусор. Сборка мусора не может быть быстрой и эффективной с точки зрения памяти, и этот факт становится очевидным, если вы посмотрите на то, как быстро работают алгоритмы GC.
Нейт СК

2
@Zathrus java может выиграть в пропускной способности b / c оптимизирующего jit, но не в задержке (в реальном времени) и, конечно, не в памяти.
gtrak 22.10.11

34

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

  • детерминированные времена жизни объектов (подсчет ссылок дает вам это, но GC нет. Хотя это может быть не так уж важно).
  • что произойдет, если деструктор сгенерирует объект при сборке мусора? Большинство языков игнорируют это исключение, поскольку на самом деле нет блока catch, чтобы можно было его транспортировать, но это, вероятно, не является приемлемым решением для C ++.
  • Как включить / отключить это? Естественно, это, вероятно, будет решение во время компиляции, но код, написанный для GC, против кода, который написан для NOT GC, будет сильно отличаться и, вероятно, несовместим. Как вы примиряете это?

Это лишь некоторые из проблем, с которыми сталкиваются.


17
Сборщик мусора и деструкторы - это решаемая проблема, приятный шаг от Бьярне. Деструкторы не бегают во время GC, потому что это не главное. GC в C ++ существует для создания понятия бесконечной памяти , а не бесконечных других ресурсов.
MSalters

2
Если деструкторы не запускаются, это полностью меняет семантику языка. Я думаю, что по крайней мере вам понадобится новое ключевое слово "gcnew" или что-то подобное, чтобы вы явно разрешили GC'у этот объект (и, следовательно, вы не должны использовать его для обёртывания ресурсов помимо памяти).
Грег Роджерс

7
Это фиктивный аргумент. Поскольку C ++ имеет явное управление памятью, вам нужно выяснить, когда каждый объект должен быть освобожден. С GC это не хуже; скорее проблема сводится к выяснению, когда освобождаются определенные объекты, а именно те объекты, которые требуют особых соображений при удалении. Опыт программирования на Java и C # показывает, что подавляющее большинство объектов не требует особых соображений и может быть безопасно оставлено для GC. Как выясняется, одной из основных функций деструкторов в C ++ является освобождение дочерних объектов, которые GC обрабатывает для вас автоматически.
Нейт СК

2
@ NateC-K: Одна вещь, которая улучшена в GC по сравнению с не-GC (возможно, самая большая вещь), - это способность надежной системы GC гарантировать, что каждая ссылка будет продолжать указывать на один и тот же объект, пока существует ссылка. Вызов Disposeобъекта может сделать его невозможным, но ссылки, которые указывали на объект, когда он был жив, будут продолжать делать это после его смерти. В отличие от этого, в системах без GC объекты могут быть удалены, пока существуют ссылки, и редко бывает какое-либо ограничение хаоса, который может быть нанесен, если одна из этих ссылок будет использована.
суперкат

22

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

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

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

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

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

Тем не менее, это сильнее, чем было предложено для C ++. Предыдущее предложение [предупреждение: PDF] (что были понижены) не гарантия вообще ничего. На 28 страницах предложения вы заметили внешне наблюдаемое поведение в виде одной (ненормативной) записки, в которой говорилось:

[Примечание: для программ, собираемых мусором, высококачественная размещенная реализация должна пытаться максимизировать объем недоступной памяти, которую она восстанавливает. —Конечная записка]

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

Даже в лучшем случае мы получаем программы, которые, основываясь на тестировании на Java , вероятно, потребуют примерно в шесть раз больше памяти для работы с той же скоростью, что и сейчас. Хуже того, сборка мусора была частью Java с самого начала - C ++ накладывает достаточно большие ограничения на сборщик мусора, чтобы он почти наверняка имел еще худшее соотношение цена / качество (даже если мы выйдем за пределы того, что предложение гарантировано, и предположим, что оно будет какая-то выгода).

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


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

2
Даже в Java GC на самом деле не определено делать что-нибудь полезное AFAIK. Это может потребовать freeвас (я имею в виду freeаналог языка Си). Но Java никогда не гарантирует вызова финализаторов или чего-то в этом роде. Фактически, C ++ делает гораздо больше, чем Java, для выполнения операций записи в базу данных, очистки файловых дескрипторов и так далее. Java утверждает, что имеет "GC", но разработчики Java должны close()все время тщательно звонить, и они должны быть очень осведомлены об управлении ресурсами, стараясь не звонить close()слишком рано или слишком поздно. C ++ освобождает нас от этого. ... (продолжение)
Аарон МакДейд

2
.. мой комментарий минуту назад не предназначен для критики Java. Я просто замечаю, что термин «сборка мусора» - очень странный термин - он означает гораздо меньше, чем думают люди, и поэтому его трудно обсудить, не понимая, что он означает.
Аарон МакДейд,

@AaronMcDaid Это правда, что GC вообще не помогает с ресурсами без памяти. К счастью, такие ресурсы выделяются довольно редко по сравнению с памятью. Более того, более 90% из них могут быть освобождены в методе, который их выделил, поэтому try (Whatever w=...) {...}решайте это (и вы получите предупреждение, когда забудете). Остальные тоже проблематичны с RAII. Вызов close()«все время» означает, может быть, один раз на десятки тысяч строк, так что это не так уж плохо, в то время как память распределяется почти на каждую строку Java.
Маартин

15

Если вы хотите автоматическую сборку мусора, есть хорошие коммерческие и общедоступные сборщики мусора для C ++. Для приложений, для которых подходит сборщик мусора, C ++ является отличным языком сборки мусора, производительность которого выгодно отличается от других языков сборки мусора. См. Язык программирования C ++ (4-е издание) для обсуждения автоматической сборки мусора в C ++. Смотрите также, Ганс-Дж. Сайт Бема для сборки мусора на C и C ++ ( архив ).

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

Источник: http://www.stroustrup.com/bs_faq.html#garbage-collection

Что касается того, почему он не имеет встроенного, если я правильно помню, что он был изобретен до того, как GC был предметом , и я не верю, что язык мог иметь GC по нескольким причинам (совместимость IE с обратной связью с C)

Надеюсь это поможет.


«с производительностью, которая выгодно отличается от других языков сборки мусора». Цитирование?
Джон Харроп

1
Моя ссылка была сломана. Я написал этот ответ 5 лет назад.
Рэйн

1
Хорошо, я надеялся на некоторую независимую проверку этих утверждений, то есть не Страуструпом или Бёмом. :-)
Джон Харроп

12

Страуструп сделал несколько хороших комментариев по этому поводу на конференции Going Native 2013 года.

Просто перейдите к 25м50 в этом видео . (Я бы порекомендовал посмотреть все видео на самом деле, но это пропускает материал о сборке мусора.)

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

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

Он показывает этот пример:

void f(int n, int x) {
    Gadget *p = new Gadget{n};
    if(x<100) throw SomeException{};
    if(x<200) return;
    delete p;
}

Это небезопасно в C ++. Но это также небезопасно в Java! В C ++, если функция возвращается рано, deleteникогда не будет вызываться. Но если у вас была полная сборка мусора, такая как в Java, вы просто получаете предположение, что объект будет уничтожен «в какой-то момент в будущем» ( Обновление: это еще хуже, чем это. Java делает НеОбещаю называть финализатор когда-либо - возможно, он никогда не будет вызван). Этого недостаточно, если гаджет содержит дескриптор открытого файла, или соединение с базой данных, или данные, которые вы буферизировали для записи в базу данных на более позднем этапе. Мы хотим, чтобы гаджет был уничтожен, как только он будет завершен, чтобы освободить эти ресурсы как можно скорее. Вы не хотите, чтобы ваш сервер баз данных боролся с тысячами соединений с базами данных, которые больше не нужны - он не знает, что ваша программа закончила работать.

Так в чем же решение? Есть несколько подходов. Очевидный подход, который вы будете использовать для подавляющего большинства ваших объектов:

void f(int n, int x) {
    Gadget p = {n};  // Just leave it on the stack (where it belongs!)
    if(x<100) throw SomeException{};
    if(x<200) return;
}

Для этого требуется меньше символов. Это не newмешает. Это не требует, чтобы вы печатали Gadgetдважды. Объект уничтожается в конце функции. Если это то, что вы хотите, это очень интуитивно понятно. Gadgets ведут себя так же, как intили double. Предсказуемый, легкий для чтения, легкий в обучении. Все это «ценность». Иногда это большая ценность, но ценности легче учить, потому что у вас нет такого «действия на расстоянии», которое вы получаете с помощью указателей (или ссылок).

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

Область и срок службы важны. В большинстве случаев проще, если время жизни совпадает с областью действия. Это легче понять и легче учить. Когда вам нужно другое время жизни, должно быть очевидно, что вы читаете код, который делаете, shared_ptrнапример. (Или возвращая (большие) объекты по значению, используя семантику перемещения или unique_ptr.

Это может показаться проблемой эффективности. Что делать, если я хочу вернуть гаджет foo()? Семантика перемещения C ++ 11 облегчает возвращение больших объектов. Просто напишите, Gadget foo() { ... }и это будет просто работать, и работать быстро. Вам не нужно связываться с &&самим собой, просто возвращайте вещи по значению, и язык часто сможет выполнить необходимые оптимизации. (Еще до C ++ 03 компиляторы проделали замечательную работу, избегая ненужного копирования.)

Как сказал Страуструп в другом месте на видео (перефразируя): «Только компьютерный специалист будет настаивать на копировании объекта, а затем уничтожении оригинала. (Аудитория смеется). Почему бы просто не переместить объект непосредственно в новое место? Это то, что люди (не компьютерные ученые) ожидать. "

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

Если это не работает для вас, вы можете использовать unique_ptr, или если это невозможно, shared_ptr. Хорошо написанный C ++ 11 короче, его легче читать и легче изучать, чем многие другие языки, когда речь заходит об управлении памятью.


1
GC следует использовать только для объектов, которые не получают ресурсы (т.е. просят другие объекты делать что-либо от их имени «до дальнейшего уведомления»). Если Gadgetне попросить что-либо еще сделать что-либо от его имени, исходный код будет совершенно безопасным в Java, если бессмысленный (для Java) deleteоператор был удален.
суперкат

@supercat, объекты со скучными деструкторами интересны. (Я не определил «скучно», но в основном деструкторы, которые никогда не нужно вызывать, за исключением освобождения памяти). Для отдельного компилятора может быть возможно shared_ptr<T>специально обработать, когда Tэто «скучно». Он может решить не управлять счетчиком ссылок для этого типа, а использовать GC. Это позволило бы использовать GC без уведомления разработчика. A shared_ptrможет просто рассматриваться как указатель GC, для подходящего T. Но в этом есть ограничения, и это замедлит работу многих программ.
Аарон МакДейд

Хорошая система типов должна иметь разные типы для объектов кучи, управляемых GC и RAII, поскольку некоторые шаблоны использования очень хорошо работают с одним и очень плохо с другим. В .NET или Java, заявление string1=string2;будет выполнять очень быстро , независимо от длины строки (это не в буквальном смысле не более чем нагрузки регистра и регистрация магазина), и не требует какой - либо блокировки , чтобы гарантировать , что если приведенное выше утверждение выполняются в то время как string2IS будучи записанным, string1будет содержать либо старое значение, либо новое значение без неопределенного поведения).
суперкат

В C ++ присвоение a shared_ptr<String>требует много скрытой синхронизации, и назначение a Stringможет вести себя странно, если переменная читается и записывается одновременно. Случаи, когда хочется писать и читать Stringодновременно, не очень распространены, но могут возникнуть, если, например, какой-то код желает сделать текущие отчеты о состоянии доступными для других потоков. В .NET и Java такие вещи просто «работают».
суперкат

1
@curiousguy ничего не изменилось, если вы не примете правильные меры предосторожности, Java по-прежнему позволяет вызывать финализатор, как только конструктор завершит работу. Вот реальный пример из жизни: « finalize () вызывается для сильно достижимых объектов в Java 8 ». Вывод состоит в том, что никогда не следует использовать эту функцию, так как почти каждый согласен быть исторической ошибкой дизайна языка. Когда мы следуем этому совету, язык обеспечивает детерминизм, который мы любим.
Хольгер

11

Потому что современный C ++ не нуждается в сборке мусора.

Ответ Bjarne Stroustrup на часто задаваемые вопросы по этому вопросу гласит :

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


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

  • Большая часть кода, связанного с владением памятью, находится в библиотеках (особенно в тех, которые предоставляют контейнеры).
  • В большинстве случаев использование кода, связанного с владением памятью, следует шаблону RAII , поэтому выделение производится при построении, а освобождение при уничтожении, что происходит при выходе из области, в которой что-то было выделено.
  • Вы не выделяете или не выделяете память напрямую .
  • Необработанные указатели не владеют памятью (если вы следовали рекомендациям), поэтому вы не сможете утечь, передав их.
  • Если вам интересно, как вы собираетесь передавать начальные адреса последовательностей значений в памяти - вы будете делать это с помощью span ; необработанный указатель не требуется.
  • Если вам действительно нужен собственный «указатель», вы используете умные указатели стандартной библиотеки C ++ - они не могут течь и прилично эффективны (хотя ABI может помешать этому). Кроме того, вы можете передать владение через границы области действия с помощью «указателей владельца» . Они необычны и должны использоваться явно; но когда они приняты - они позволяют проводить хорошую статическую проверку на предмет утечек.

"О да? Но как насчет ...

... если я просто напишу код так, как мы привыкли писать C ++ в старые времена? "

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

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

... если я reintrepret_cast? Или сделать сложную указательную арифметику? Или другие такие хаки?

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

  1. Вы бы делали это редко (с точки зрения мест в коде, не обязательно с точки зрения доли времени выполнения)
  2. Вы бы сделали это только намеренно, а не случайно.
  3. Это будет выделяться в кодовой базе, соответствующей рекомендациям.
  4. Это тот код, в котором вы все равно обойдете GC на другом языке.

... развитие библиотеки? "

Если вы разработчик библиотеки C ++, то вы пишете небезопасный код с использованием необработанных указателей, и вам необходимо кодировать аккуратно и ответственно - но это отдельные фрагменты кода, написанные экспертами (и, что более важно, проверенные экспертами).


Так вот, как сказал Бьярне: на самом деле вообще нет мотивации собирать мусор, как вы все, но старайтесь не производить мусор. GC становится не проблема с C ++.

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


Что ж, это нужно (нужен GC), если вы шлифуете строки. Представьте, что у вас есть большие массивы строк (например, сотни мегабайт), которые вы строите по частям, затем обрабатываете и перестраиваете на разные длины, удаляете неиспользуемые, объединяете другие и т. Д. I знаю, потому что мне пришлось переключиться на языки высокого уровня, чтобы справиться. (Конечно, вы также можете создать свой собственный GC).
www-0av-Com

2
@ user1863152: Это тот случай, когда будет полезен пользовательский распределитель. Это по-прежнему не требует языковой целостности GC ...
einpoklum

в айнпоклум: правда. Это просто лошадь для курсов. Моим требованием была обработка динамически меняющихся галлонов информации о пассажирах транспорта. Увлекательная тема .. Действительно сводится к философии программного обеспечения.
www-0av-Com

GC, как обнаружили мир Java и .NET, наконец, имеет огромную проблему - он не масштабируется. Когда у вас в памяти находятся миллиарды живых объектов, как мы делаем сейчас с любым нетривиальным программным обеспечением, вам придется начинать писать код, чтобы скрыть вещи от GC. Бремя иметь GC в Java и .NET.
Зак видел

10

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

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


2
Я не думаю, что GC требует VM. Компилятор может добавить код ко всем операциям с указателями для обновления глобального состояния, в то время как отдельный поток работает в фоновом режиме, удаляя объекты по мере необходимости.
user83255

3
Я согласен. Вам не нужна виртуальная машина, но в тот момент, когда вы начинаете что-то управлять своей памятью, например, в фоновом режиме, я чувствую, что вы оставили настоящие «электрические провода» и у вас возникла ситуация с виртуальной машиной.
Ури


4

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

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

Интересная особенность многих сборок мусора состоит в том, что ссылка на объект определяется не битовыми шаблонами, содержащимися в нем, а отношениями между битами, содержащимися в ссылке на объект, и другой информацией, хранящейся в другом месте. В C и C ++, если битовый шаблон, хранящийся в указателе, идентифицирует объект, этот битовый шаблон будет идентифицировать этот объект, пока объект не будет явно уничтожен. В типичной системе GC объект может быть представлен битовым шаблоном 0x1234ABCD в один момент времени, но следующий цикл GC может заменить все ссылки на 0x1234ABCD ссылками на 0x4321BABE, после чего объект будет представлен последним шаблоном. Даже если нужно отобразить битовый шаблон, связанный со ссылкой на объект, а затем прочитать его обратно с клавиатуры,


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

@PasserBy: Интересно, сколько приложений, использующих 64-разрядные указатели, выиграют больше от использования масштабированных 32-разрядных указателей в качестве ссылок на объекты или от сохранения почти всего в 4 ГБ адресного пространства и от использования специальных объектов для хранения / извлечения данных из больших хранение вне пределов? Машины имеют достаточно ОЗУ, поэтому потребление ОЗУ 64-разрядными указателями может не иметь значения, за исключением того, что они поглощают вдвое больше кэш-памяти, чем 32-разрядные указатели.
суперкат

3

Все технические разговоры слишком усложняют концепцию.

Если вы автоматически включите GC в C ++ для всей памяти, тогда рассмотрите что-то вроде веб-браузера. Веб-браузер должен загрузить полный веб-документ и запустить веб-скрипты. Вы можете хранить переменные веб-скрипта в дереве документа. В БОЛЬШОМ документе в браузере с большим количеством открытых вкладок это означает, что каждый раз, когда сборщик мусора должен сделать полную коллекцию, он также должен сканировать все элементы документа.

На большинстве компьютеров это означает, что PAGE FAULTS произойдет. Таким образом, основная причина, чтобы ответить на этот вопрос, заключается в том, что ошибки страницы произойдут. Вы будете знать это, как когда ваш компьютер начнет делать много доступа к диску. Это потому, что GC должен коснуться большого количества памяти, чтобы доказать недействительные указатели. Когда у вас есть добросовестное приложение, использующее много памяти, необходимость сканировать все объекты каждой коллекции приводит к хаосу из-за ошибок страницы. Ошибка страницы - это когда виртуальная память должна быть прочитана обратно в оперативную память с диска.

Таким образом, правильное решение состоит в том, чтобы разделить приложение на части, которые нуждаются в GC, и части, которые не нуждаются. В приведенном выше примере с веб-браузером, если дерево документа было выделено с помощью malloc, но javascript работал с GC, то каждый раз, когда GC запускает его, сканируется только небольшая часть памяти и все элементы PAGED OUT памяти для дерево документов не нужно возвращать обратно.

Чтобы лучше понять эту проблему, посмотрите на виртуальную память и то, как она реализована на компьютерах. Все дело в том, что 2 ГБ доступно программе, когда на самом деле не так много оперативной памяти. На современных компьютерах с 2 ГБ ОЗУ для 32-битной системы это не такая проблема, если запущена только одна программа.

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

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

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


3

КОРОТКИЙ ОТВЕТ: Мы не знаем, как эффективно выполнять сборку мусора (с небольшими временными и пространственными издержками) и правильно все время (во всех возможных случаях).

ДОЛГО ОТВЕТ: Как и C, C ++ - системный язык; это означает, что он используется, когда вы пишете системный код, например, операционную систему. Другими словами, C ++, как и C, разработан с максимально возможной производительностью в качестве основной цели. Стандарт языка не добавляет никаких функций, которые могут помешать достижению цели.

Это ставит на паузу вопрос: почему сборка мусора снижает производительность? Основная причина в том, что когда речь идет о реализации, мы [компьютерные специалисты] не знаем, как выполнять сборку мусора с минимальными издержками во всех случаях. Следовательно, компилятору C ++ и системе времени выполнения невозможно постоянно выполнять сборку мусора. С другой стороны, программист C ++ должен знать свой дизайн / реализацию, и он - лучший человек, который решает, как лучше всего выполнять сборку мусора.

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


3

Когда мы сравниваем C ++ с Java, мы видим, что C ++ не был разработан с неявной сборкой мусора, в то время как Java была.

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

В дополнение к этому, C ++ - это язык, который предназначен для запуска в качестве автономного исполняемого файла вместо сложной среды выполнения.

В общем: да, возможно, можно добавить сборщик мусора в C ++, но ради преемственности лучше этого не делать.


1
Освобождение памяти и запуск деструкторов - слишком отдельные проблемы. (У Java нет деструкторов, то есть PITA.) GC освобождает память, она не запускает dtors.
любопытный парень

0

Главным образом по двум причинам:

  1. Потому что это не нужно (ИМХО)
  2. Потому что это в значительной степени несовместимо с RAII, который является краеугольным камнем C ++

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


Существуют многочисленные (более новые) алгоритмы, которые по своей природе сложно реализовать без сборки мусора. Время шло. Инновации также происходят от новых идей, которые хорошо подходят для (сбора мусора) языков высокого уровня. Попробуйте сделать бэкпорт любого из них на C ++ без GC, вы заметите неровности на дороге. (Я знаю, что должен привести примеры, но сейчас я как бы тороплюсь. Извините. Одна из них, о которой я могу подумать, сейчас вращается вокруг постоянных структур данных, где подсчет ссылок не будет работать.).
BitTickler

0

Внедрение сбора мусора - это действительно сдвиг парадигмы от низкого уровня к высокому уровню.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.