Как давний программист на C #, я недавно узнал больше о преимуществах Resource Acquisition Is Initialization (RAII). В частности, я обнаружил, что идиома C #:
using (var dbConn = new DbConnection(connStr)) {
// do stuff with dbConn
}
имеет эквивалент C ++:
{
DbConnection dbConn(connStr);
// do stuff with dbConn
}
Это означает, что в C ++ не нужно помнить об использовании ресурсов, как DbConnection
в using
блоке! Это кажется основным преимуществом C ++. Это еще более убедительно, когда вы рассматриваете класс, который имеет член экземпляра типа DbConnection
, например
class Foo {
DbConnection dbConn;
// ...
}
В C # мне нужно было бы реализовать Foo IDisposable
как таковой:
class Foo : IDisposable {
DbConnection dbConn;
public void Dispose()
{
dbConn.Dispose();
}
}
и что еще хуже, каждый пользователь Foo
должен помнить, чтобы заключить Foo
в using
блок, например:
using (var foo = new Foo()) {
// do stuff with "foo"
}
Теперь, глядя на C # и его корни Java, мне интересно ... разработчики Java полностью оценили то, что они отказались, когда они отказались от стека в пользу кучи, отказавшись от RAII?
(Точно так же, Страуструп в полной мере оценил значение RAII?)
источник
Ответы:
Я почти уверен, что Гослинг не понял значение RAII во время разработки Java. В своих интервью он часто говорил о причинах отказа от генериков и перегрузок операторов, но никогда не упоминал детерминированные деструкторы и RAII.
Как ни странно, даже Страуструп не знал о важности детерминированных деструкторов в то время, когда разрабатывал их. Я не могу найти цитату, но если вы действительно в нее влюблены, вы можете найти ее среди его интервью здесь: http://www.stroustrup.com/interviews.html
источник
manual
управления памятью. Они больше похожи на детерминированный сборщик мусора, контролируемый мелкими зернами. При правильном использовании умные указатели являются коленями пчел.Да, разработчики C # (и, я уверен, Java) специально отказались от детерминированной финализации. Я спрашивал Андерса Хейлсберга об этом несколько раз в 1999-2002 годах.
Во-первых, идея различной семантики для объекта, основанного на том, является ли он на основе стека или кучи, безусловно, противоречит объединяющей цели разработки обоих языков, которая заключалась в том, чтобы избавить программистов именно от таких проблем.
Во-вторых, даже если вы признаете, что есть преимущества, в бухгалтерском учете возникают значительные сложности и недостатки в реализации. Вы действительно не можете помещать подобные стеку объекты в стек на управляемом языке. Вам остается сказать «подобная стеку семантика» и выполнить значительную работу (типы значений уже достаточно сложны, подумайте об объекте, который является экземпляром сложного класса, со ссылками, входящими и возвращающимися в управляемую память).
Из-за этого вам не нужно детерминированное завершение для каждого объекта в системе программирования, где «(почти) все является объектом». Таким образом , вы действительно должны ввести какое - то программист управляемого синтаксиса , чтобы отделить нормально гусеничный объект из одного , который имеет детерминированное завершение.
В C # у вас есть
using
ключевое слово, которое появилось довольно поздно при разработке того, что стало C # 1.0. ВсеIDisposable
это довольно жалко, и возникает вопрос: не будет ли более элегантноusing
работать с синтаксисом деструктора C ++,~
отмечая те классы, к которымIDisposable
может автоматически применяться шаблон рабочей таблицы?источник
~
синтаксис, чтобы быть синтаксическим сахаром дляIDisposable.Dispose()
~
качестве синтаксического сахараIDisposable.Dispose()
, и это гораздо удобнее, чем синтаксис C #.Имейте в виду, что Java была разработана в 1991-1995 годах, когда C ++ был совершенно другим языком. Исключения (которые сделали RAII необходимыми ) и шаблоны (которые облегчили реализацию умных указателей) были «новомодными» функциями. Большинство программистов на C ++ пришли из C и привыкли к ручному управлению памятью.
Поэтому я сомневаюсь, что разработчики Java сознательно решили отказаться от RAII. Однако для Java было преднамеренным решением отдать предпочтение семантике ссылок вместо семантики значений. Детерминированное разрушение трудно реализовать на языке эталонной семантики.
Так зачем использовать семантику ссылок вместо семантики значений?
Потому что это делает язык намного проще.
Foo
иFoo*
или междуfoo.bar
иfoo->bar
.clone()
. Многие объекты просто не нужно копировать. Например, неизменяемые не делают.)private
конструкторы копирования иoperator=
делать класс не копируемым. Если вы не хотите, чтобы объекты класса копировались, вы просто не пишете функцию для копирования.swap
функций. (Если вы не пишете процедуру сортировки.)Основным недостатком семантики ссылок является то, что когда каждый объект потенциально имеет несколько ссылок на него, становится трудно понять, когда его удалить. Вы в значительной степени должны иметь автоматическое управление памятью.
Java решила использовать недетерминированный сборщик мусора.
Разве GC не может быть детерминированным?
Да, оно может. Например, реализация Python на C использует подсчет ссылок. А позже добавили трассировку GC для обработки циклического мусора, где сбой refcounts.
Но пересчет ужасно неэффективен. Много циклов ЦП провел обновление счетчиков. Еще хуже в многопоточной среде (например, для Java), где эти обновления должны быть синхронизированы. Гораздо лучше использовать нулевой сборщик мусора, пока вам не нужно переключиться на другой.
Можно сказать, что Java решила оптимизировать общий случай (память) за счет не заменимых ресурсов, таких как файлы и сокеты. Сегодня, в свете принятия RAII в C ++, это может показаться неправильным выбором. Но помните, что большую часть целевой аудитории для Java составляли программисты на C (или «C с классами»), которые привыкли явно закрывать эти вещи.
Но как насчет C ++ / CLI "стековых объектов"?
Они просто синтаксический сахар для
Dispose
( оригинальная ссылка ), очень похожий на C #using
. Тем не менее, это не решает общую проблему детерминированного уничтожения, потому что вы можете создать анонимного,gcnew FileStream("filename.ext")
а C ++ / CLI не удалит его автоматически.источник
using
утверждение прекрасно справляется со многими проблемами, связанными с очисткой, но многие другие остаются. Я бы предположил, что правильным подходом к языку и структуре будет декларативное различие между хранилищами, которые «владеют» ссылочными ссылками, иIDisposable
теми, на которые нет ссылок ; перезапись или отказ от места хранения, которому принадлежит ссылка,IDisposable
должны ликвидировать цель в отсутствие директивы об обратном.new Date(oldDate.getTime())
.Java7 представила нечто похожее на C #
using
: оператор try-with-resourcesТак что я думаю, что они либо сознательно не решили не применять RAII, либо тем временем передумали.
источник
java.lang.AutoCloseable
. Вероятно, не имеет большого значения, но мне не нравится, как это выглядит несколько ограниченным. Может быть, у меня есть какой-то другой объект, который должен вызываться автоматически, но это очень семантически странно, чтобы заставить его реализоватьAutoCloseable
...using
это не то же самое, что RAII - в одном случае вызывающий абонент беспокоится об утилизации ресурсов, а в другом - вызываемый.using
/ try-with-resources не совпадает с RAII.using
и это похоже далеко не RAII.Java намеренно не имеет основанных на стеке объектов (также называемых объектами-значениями). Это необходимо для автоматического уничтожения объекта в конце метода.
Из-за этого и того факта, что Java является сборщиком мусора, детерминированная финализация более или менее невозможна (например, что, если на мой «локальный» объект ссылаются где-то еще? Затем, когда метод заканчивается, мы не хотим, чтобы он разрушался ) .
Тем не менее, это нормально для большинства из нас, потому что почти никогда не требуется детерминированная финализация, за исключением случаев взаимодействия с нативными (C ++) ресурсами!
Почему в Java нет стековых объектов?
(Кроме примитивов ..)
Потому что основанные на стеке объекты имеют другую семантику, чем ссылки на основе кучи. Представьте себе следующий код на C ++; Что это делает?
myObject
это локальный стековый объект, вызывается конструктор копирования (если результат назначен чему-либо).myObject
это локальный стековый объект, и мы возвращаем ссылку, результат не определен.myObject
это член / глобальный объект, вызывается конструктор копирования (если результат назначен чему-либо).myObject
это член / глобальный объект, и мы возвращаем ссылку, ссылка возвращается.myObject
указатель на локальный стековый объект, результат не определен.myObject
указатель на член / глобальный объект, этот указатель возвращается.myObject
указатель на объект на основе кучи, этот указатель возвращается.Что же делает код в Java?
myObject
возвращается. Не имеет значения, является ли переменная локальной, членской или глобальной; и нет никаких основанных на стеке объектов или случаев указателя, о которых нужно беспокоиться.Выше показано, почему основанные на стеке объекты являются очень распространенной причиной ошибок программирования в C ++. Из-за этого дизайнеры Java убрали их; и без них нет смысла использовать RAII в Java.
источник
Ваше описание дыр
using
неполное. Рассмотрим следующую проблему:На мой взгляд, отсутствие RAII и GC было плохой идеей. Когда дело доходит до закрытия файлов в Java, это
malloc()
иfree()
там.источник
using
пункт - большой шаг вперед для C # по сравнению с Java. Он допускает детерминированное разрушение и, следовательно, корректирует управление ресурсами (это не так хорошо, как RAII, как вы должны помнить, чтобы сделать это, но это определенно хорошая идея).free()
вfinally
.IEnumerable
не наследовал отIDisposable
, и было множество специальных итераторов, которые никогда не могли быть реализованы в результате.Я довольно старый. Я был там и видел это, и много раз ударил головой об этом.
Я был на конференции в Херсли-Парке, где ребята из IBM рассказывали нам, как прекрасен этот совершенно новый язык Java, только кто-то спросил ... почему нет деструктора для этих объектов. Он не имел в виду то, что мы знаем как деструктор в C ++, но финализатора тоже не было (или у него были финализаторы, но они в основном не работали). Это путь назад, и мы решили, что в тот момент Java была немного игрушечным языком.
теперь они добавили Finalisers в спецификацию языка, и Java получила некоторое распространение.
Конечно, позже всем сказали не ставить финализаторы на свои объекты, потому что это сильно замедлило GC. (поскольку он должен был не только заблокировать кучу, но и переместить объекты, подлежащие финализации, во временную область, поскольку эти методы нельзя было вызвать, так как сборщик мусора приостановил работу приложения. Вместо этого они будут вызваны непосредственно перед следующей Цикл GC) (и что еще хуже, иногда финализатор никогда не вызывался бы вообще, когда приложение закрывалось. Представьте, что у вас никогда не было закрытого дескриптора файла)
Потом у нас был C #, и я помню дискуссионный форум на MSDN, где нам рассказывали, как прекрасен этот новый язык C #. Кто-то спросил, почему не было детерминированной финализации, и ребята из MS рассказали нам, как нам не нужны такие вещи, затем сказали, что нам нужно изменить наш способ разработки приложений, а затем сказали, как прекрасен GC и как все наши старые приложения были мусор и никогда не работал из-за всех циркулярных ссылок. Затем они уступили давлению и сказали нам, что они добавили этот шаблон IDispose к спецификации, которую мы могли бы использовать. Я думал, что это было в значительной степени назад к ручному управлению памятью для нас в приложениях C # на тот момент.
Конечно, ребята из MS позже обнаружили, что все, что они сказали нам, это ... ну, они сделали IDispose немного больше, чем просто стандартный интерфейс, а позже добавили оператор using. W00t! Они поняли, что детерминистская финализация чего-то не хватает в языке в конце концов. Конечно, вы все равно должны помнить, чтобы вставить его везде, так что это все еще немного ручной, но это лучше.
Так почему же они сделали это, когда могли с самого начала автоматически размещать семантику в стиле использования в каждом блоке области видимости? Вероятно, эффективность, но мне нравится думать, что они просто не осознали. Также как в конце концов они поняли, что вам все еще нужны умные указатели в .NET (Google SafeHandle), они подумали, что GC действительно решит все проблемы. Они забыли, что объект - это больше, чем просто память, и что GC в первую очередь предназначен для управления памятью. они увлеклись идеей, что GC справится с этим, и забыли, что вы помещаете туда другие вещи, объект не просто капля памяти, которая не имеет значения, если вы не удалите ее на некоторое время.
Но я также думаю, что отсутствие метода finalize в исходной Java имело немного больше - что все объекты, которые вы создавали, были связаны с памятью, и если вы хотите удалить что-то еще (например, дескриптор БД, сокет или что-то еще) ) тогда вы должны были сделать это вручную .
Помните, что Java была разработана для встраиваемых сред, где люди привыкли писать код на C с большим количеством ручных распределений, поэтому отсутствие автоматического освобождения не было большой проблемой - они никогда не делали этого раньше, так зачем вам это нужно в Java? Проблема заключалась не в том, что связано с потоками или стеком / кучей, а просто с тем, чтобы сделать выделение памяти (и, следовательно, удаление) немного проще. В общем, оператор try / finally, вероятно, является лучшим местом для обработки ресурсов, не связанных с памятью.
ИМХО, то, как .NET просто скопировал самый большой недостаток Java - это его самая большая слабость. .NET должен был быть лучше C ++, а не лучше Java.
источник
Dispose
всех полей, помеченныхusing
директивой, и указания,IDisposable.Dispose
должен ли он автоматически вызываться; (3) директива, аналогичнаяusing
, но которая будет вызываться толькоDispose
в случае исключения; (4) изменение,IDisposable
которое будет приниматьException
параметр, и ...using
при необходимости; параметр был бы,null
еслиusing
блок вышел нормально, или иначе указал бы, какое исключение ожидало, если оно вышло через исключение. Если бы такие вещи существовали, было бы намного проще эффективно управлять ресурсами и избежать утечек.Брюс Экель, автор книг «Мышление в Java» и «Мышление в C ++» и член Комитета по стандартам C ++, считает, что во многих областях (не только в RAII) Гослинг и команда Java не домашнее задание.
источник
Лучшая причина гораздо проще, чем большинство ответов здесь.
Вы не можете передавать выделенные объекты стека другим потокам.
Остановись и подумай об этом. Продолжайте думать .... Теперь в C ++ не было потоков, когда все так увлеклись RAII. Даже Erlang (отдельные кучи на поток) становится неприглядным, когда вы передаете слишком много объектов. C ++ получил модель памяти только в C ++ 2011; теперь вы можете почти рассуждать о параллелизме в C ++, не обращаясь к «документации» вашего компилятора.
Java была разработана с (почти) первого дня для нескольких потоков.
У меня все еще есть моя старая копия "языка программирования C ++", где Страуструп уверяет меня, что мне не понадобятся потоки.
Вторая болезненная причина - избегать нарезки.
источник
В C ++ для реализации высокоуровневого объекта (RAII) вы используете более общие языковые функции более низкого уровня (деструкторы, автоматически вызываемые для объектов на основе стека), и этот подход, как кажется, не подходит людям C # / Java. слишком любит Они предпочитают разрабатывать специальные инструменты высокого уровня для конкретных нужд и предоставлять их готовым программистам, встроенным в язык. Проблема таких специфических инструментов заключается в том, что их часто невозможно настроить (отчасти это то, что делает их такими простыми в освоении). При сборке из меньших блоков лучшее решение может прийти со временем, а если у вас есть только встроенные конструкции высокого уровня, это менее вероятно.
Так что да, я думаю (на самом деле меня там не было ...) это было осознанное решение с целью облегчить изучение языков, но, на мой взгляд, это было плохое решение. С другой стороны, я обычно предпочитаю философию C ++ «дай программистам шанс выпустить их собственную катушку», поэтому я немного предвзят.
источник
Вы уже вызвали грубый эквивалент этого в C # с помощью
Dispose
метода. Ява также имеетfinalize
. ПРИМЕЧАНИЕ: я понимаю, что финализация Java не является детерминированной и отличается от нееDispose
, я просто указываю, что у них обоих есть метод очистки ресурсов наряду с GC.Во всяком случае, C ++ становится более болезненным, потому что объект должен быть физически уничтожен. В языках более высокого уровня, таких как C # и Java, мы зависим от сборщика мусора, который очищает его, когда на него больше нет ссылок. Нет такой гарантии, что объект DBConnection в C ++ не имеет мошеннических ссылок или указателей на него.
Да, код C ++ может быть более интуитивно понятным для чтения, но может быть кошмаром для отладки, потому что границы и ограничения, которые устанавливают языки, такие как Java, исключают некоторые из самых отягчающих и сложных ошибок, а также защищают других разработчиков от распространенных ошибок новичков.
Возможно, все сводится к предпочтениям, таким как низкоуровневая мощность, контроль и чистота C ++, где другие, как я, предпочитают более песочничный язык, который гораздо более явный.
источник