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

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

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

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

Ответ 1

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

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

С++ имеет RAII, и я всегда жалуюсь, что не нашел RAII (или кастрированный RAII) в Garbage Collected languages.

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

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

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

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

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

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

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

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

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

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

Заключение

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

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

Ответ 2

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

Этот вопрос кажется аналогичным: "Что предлагает С++ для опытных разработчиков ассемблера? Инструкции и подпрограммы устраняют необходимость в нем, правильно?"

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

Во-первых, RAII не является доказательством пули: он работает против некоторых общих сбоев, которые распространены на С++, но есть много случаев, когда 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;
                }
        }
}

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

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

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

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

Ответ 7

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

Не нужно преследовать утечки ресурсов в коде менее опытных коллег.

Ответ 8

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

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

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

Сложная вещь о GC решает, когда объект больше не нужен.

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

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

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

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

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


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

Ответ 13

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

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

Ответ 14

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

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

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

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

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

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

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

Ответ 15

В рамках, поддерживающей GC, ссылка на неизменяемый объект, такой как строка, может передаваться так же, как и примитив. Рассмотрим класс (С# или 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. Синхронизация потоков потребовалась бы, если бы требовалось обеспечить связь между именем максимального элемента и его значением, но безопасность памяти гарантирована даже в ее отсутствие.

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

Ответ 16

Сбор мусора может привести к утечке вашего худшего кошмара

Полноценный GC, который обрабатывает такие вещи, как циклические ссылки, будет скорее обновлением по сравнению с shared_ptr. Я бы несколько приветствовал его на С++, но не на языковом уровне.

Одна из красавиц о С++ заключается в том, что она не заставляет мусорную коллекцию на вас.

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

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

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

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

Учитывая некоторый ресурс R, в командной среде, где разработчики не постоянно обмениваются друг с другом и тщательно анализируют каждый код (что-то слишком распространенное в моем опыте), разработчику становится довольно легко A, чтобы сохранить дескриптор этого ресурса. Разработчик B делает также, возможно, неясным способом, который косвенно добавляет R к некоторой структуре данных. Таким образом, C. В сборке мусора это создало 3 владельцев R.

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

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

Без сбора мусора разработчик C создал бы висячий указатель. Он может попытаться получить к нему доступ в какой-то момент и привести к сбою программного обеспечения. Теперь, когда ошибка тестирования/пользователя видима. C немного смущается и исправляет свою ошибку. В сценарии GC просто попытка выяснить, где протекает система, может быть настолько сложной, что некоторые утечки никогда не исправляются. Это не физические течи valgrind, которые могут быть легко обнаружены и определены в определенной строке кода.

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

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

В обоих случаях мы говорим о постоянных объектах, не находящихся в стеке, таких как граф сцены в 3D-программном обеспечении или видеоклипы, доступные в композиторе или противниках в игровом мире. Когда ресурсы привязывают их время жизни к стеку, как С++, так и любой другой язык GC, как правило, делают тривиальным управление ресурсами. Реальная трудность заключается в постоянных ресурсах, ссылающихся на другие ресурсы.

В C или С++ у вас могут возникнуть зависающие указатели и сбои, возникающие из-за segfaults, если вы не можете четко определить, кто владеет ресурсом, и когда дескрипторы для них должны быть выпущены (например: set to null в ответ на событие). Тем не менее, в GC этот громкий и неприятный, но часто простой сбойный сигнал обменивается на тихую утечку ресурсов, которая никогда не может быть обнаружена.