Почему в C ++ нет сборщика мусора? - PullRequest
253 голосов
/ 29 сентября 2008

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

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

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

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

Ответы [ 16 ]

149 голосов
/ 29 сентября 2008

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

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

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

Хорошее обсуждение темы здесь .

Общий обзор:

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

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

Существует 2 типа сборки мусора ...

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

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

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

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

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

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

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

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

137 голосов
/ 24 февраля 2010

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

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

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

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

В настоящее время широко используются 2 семейства GC: * ​​1012 *

  • Маркировка и подметание
  • Справочно-счетный вид

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

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

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

2. Приобретение ресурсов является инициализацией

В 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 ... однако это не тривиальная проблема. А пока нам просто нужно закатать рукава.

53 голосов
/ 29 сентября 2008

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

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

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

33 голосов
/ 29 сентября 2008

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

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

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

19 голосов
/ 13 июня 2011

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

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

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

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

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

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

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

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

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

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

15 голосов
/ 29 сентября 2008

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

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

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

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

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

12 голосов
/ 19 октября 2013

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

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

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

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

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

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 дважды. Объект уничтожается в конце функции. Если это то, что вы хотите, это очень интуитивно понятно. Gadget s ведут себя так же, как int или double. Предсказуемый, легкий для чтения, легкий в обучении. Все это «ценность». Иногда это большая ценность, но ценности легче учить, потому что у вас нет такого «действия на расстоянии», которое вы получаете с помощью указателей (или ссылок).

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

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

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

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

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

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

10 голосов
/ 29 сентября 2008

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

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

8 голосов
/ 29 сентября 2008

Чтобы ответить на большинство вопросов «почему» о C ++, прочитайте Дизайн и развитие C ++

5 голосов
/ 01 января 2018

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

FAQ Бьярна Страуструпа ответ на этот вопрос гласит :

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


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

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

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

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

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

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

... если я reintrepret_cast? Или сделать указатель арифметики? Или другие подобные хаки? "

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

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

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

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


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

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

...