Разработчики Java сознательно отказались от RAII?

82

Как давний программист на 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?)

JoelFan
источник
5
Я не уверен, о чем вы говорите, не включая ресурсы в C ++. Объект DBConnection, вероятно, обрабатывает закрытие всех ресурсов в своем деструкторе.
maple_shaft
16
@maple_shaft, точно моя точка зрения! Это преимущество C ++, которое я рассматриваю в этом вопросе. В C # вам нужно заключать ресурсы в «использование» ... в C ++ вы этого не делаете.
JoelFan
12
Насколько я понимаю, RAII, как стратегия, была понята только тогда, когда компиляторы C ++ были достаточно хороши, чтобы фактически использовать расширенные шаблоны, что намного позже Java. C ++, который был фактически доступен для использования при создании Java, был очень примитивным, в стиле «C с классами», возможно , с базовыми шаблонами, если вам повезло.
Шон Макмиллан
6
«Я понимаю, что RAII, как стратегия, была понята только тогда, когда компиляторы C ++ были достаточно хороши, чтобы фактически использовать расширенные шаблоны, что намного лучше, чем Java». - Это не совсем правильно. Конструкторы и деструкторы были основными функциями C ++ с самого первого дня, задолго до широкого использования шаблонов и задолго до Java.
Джим в Техасе
8
@JimInTexas: Я думаю, что у Шона где-то есть базовое семя истины (хотя суть не в шаблонах, а в исключениях). Конструкторы / Деструкторы были там с самого начала, но там важность и концепция RAII изначально не была реализована (какое слово я ищу). Потребовалось несколько лет и некоторое время, чтобы компиляторы поправились, прежде чем мы поняли, насколько важен весь RAII.
Мартин Йорк,

Ответы:

38

Теперь, глядя на C # и его корни Java, мне интересно ... разработчики Java полностью оценили то, что они отказались, когда они отказались от стека в пользу кучи, отказавшись от RAII?

(Точно так же, Страуструп в полной мере оценил значение RAII?)

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

Как ни странно, даже Страуструп не знал о важности детерминированных деструкторов в то время, когда разрабатывал их. Я не могу найти цитату, но если вы действительно в нее влюблены, вы можете найти ее среди его интервью здесь: http://www.stroustrup.com/interviews.html

Неманья Трифунович
источник
11
@maple_shaft: Короче говоря, это невозможно. За исключением случаев, когда вы изобрели способ иметь детерминированную сборку мусора (что в общем случае кажется невозможным и в любом случае делает недействительными все оптимизации GC за последние десятилетия), вам придется вводить объекты, выделенные стеком, но это открывает несколько банок черви: эти объекты нуждаются в семантике, «проблеме секционирования» с подтипами (и, следовательно, без полиморфизма), висячих указателях, если, возможно, если вы не наложите на них существенные ограничения или не внесете значительные несовместимые изменения системы типов. И это только с моей головы.
13
@DeadMG: Итак, вы предлагаете вернуться к ручному управлению памятью. Это правильный подход к программированию в целом, и, конечно, он допускает детерминированное разрушение. Но это не отвечает на этот вопрос, который касается настройки GC-only, которая хочет обеспечить безопасность памяти и четко определенное поведение, даже если мы все ведем себя как идиоты. Это требует GC для всего и никакого способа запустить уничтожение объекта вручную (и весь Java-код в existance зависит, по крайней мере, от первого), так что либо вы делаете GC детерминированным, либо вам не повезло.
26
@delan. Я бы не назвал C ++ умными указателями manualуправления памятью. Они больше похожи на детерминированный сборщик мусора, контролируемый мелкими зернами. При правильном использовании умные указатели являются коленями пчел.
Мартин Йорк,
10
@LokiAstari: Ну, я бы сказал, что они немного менее автоматичны, чем полноценные GC (вам нужно подумать, какой тип умственности вы на самом деле хотите), и для их реализации в виде библиотеки требуются необработанные указатели (и, следовательно, ручное управление памятью) для построения на них. , Кроме того, я не знаю ни одного умного указателя, который автоматически обрабатывает циклические ссылки, что является строгим требованием для сборки мусора в моих книгах. Умные указатели, безусловно, невероятно крутые и полезные, но вы должны признать, что они не могут предоставить некоторые гарантии (независимо от того, считаете ли вы их полезными) полностью и исключительно языка GC.
11
@ Делан: Я должен не согласиться там. Я думаю, что они более автоматические, чем GC, поскольку они детерминированные. ХОРОШО. Чтобы быть эффективным, вы должны убедиться, что вы используете правильный (я дам вам это). std :: weak_ptr отлично справляется с циклами. Циклы всегда выбрасываются, но в действительности это вряд ли когда-либо проблема, потому что базовый объект обычно основан на стеке, и когда это происходит, он убирает все остальное. В редких случаях это может быть проблемой std :: weak_ptr.
Мартин Йорк,
60

Да, разработчики C # (и, я уверен, Java) специально отказались от детерминированной финализации. Я спрашивал Андерса Хейлсберга об этом несколько раз в 1999-2002 годах.

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

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

Из-за этого вам не нужно детерминированное завершение для каждого объекта в системе программирования, где «(почти) все является объектом». Таким образом , вы действительно должны ввести какое - то программист управляемого синтаксиса , чтобы отделить нормально гусеничный объект из одного , который имеет детерминированное завершение.

В C # у вас есть usingключевое слово, которое появилось довольно поздно при разработке того, что стало C # 1.0. Все IDisposableэто довольно жалко, и возникает вопрос: не будет ли более элегантно usingработать с синтаксисом деструктора C ++, ~отмечая те классы, к которым IDisposableможет автоматически применяться шаблон рабочей таблицы?

Ларри Обриен
источник
2
Как насчет того, что сделал C ++ / CLI (.NET), где объекты в управляемой куче также имеют основанный на стеке «дескриптор», который обеспечивает RIAA?
JoelFan
3
C ++ / CLI имеет совершенно другой набор дизайнерских решений и ограничений. Некоторые из этих решений означают, что вы можете потребовать от программистов большего внимания к распределению памяти и влиянию на производительность: весь компромисс «дай им достаточно веревки, чтобы повеситься». И я думаю, что компилятор C ++ / CLI значительно сложнее, чем компилятор C # (особенно в его ранних поколениях).
Ларри OBrien
5
+1 это пока единственный правильный ответ - потому что у Java намеренно нет (не примитивных) объектов на основе стека.
BlueRaja - Дэнни Пфлюгофт
8
@ Питер Тейлор - верно. Но я чувствую, что недетерминированный деструктор C # стоит очень мало, так как вы не можете полагаться на него для управления каким-либо ограниченным ресурсом. Так что, по моему мнению, было бы лучше использовать ~синтаксис, чтобы быть синтаксическим сахаром дляIDisposable.Dispose()
Ларри OBrien
3
@Larry: я согласен. C ++ / CLI действительно использует в ~качестве синтаксического сахара IDisposable.Dispose(), и это гораздо удобнее, чем синтаксис C #.
dan04
41

Имейте в виду, что Java была разработана в 1991-1995 годах, когда C ++ был совершенно другим языком. Исключения (которые сделали RAII необходимыми ) и шаблоны (которые облегчили реализацию умных указателей) были «новомодными» функциями. Большинство программистов на C ++ пришли из C и привыкли к ручному управлению памятью.

Поэтому я сомневаюсь, что разработчики Java сознательно решили отказаться от RAII. Однако для Java было преднамеренным решением отдать предпочтение семантике ссылок вместо семантики значений. Детерминированное разрушение трудно реализовать на языке эталонной семантики.

Так зачем использовать семантику ссылок вместо семантики значений?

Потому что это делает язык намного проще.

  • Нет необходимости проводить синтаксическое различие между Fooи Foo*или между foo.barи foo->bar.
  • Нет необходимости в перегруженном назначении, когда все назначение выполняет копирование указателя.
  • Нет необходимости в конструкторах копирования. ( Иногда возникает необходимость в явной функции копирования, такой как clone(). Многие объекты просто не нужно копировать. Например, неизменяемые не делают.)
  • Нет необходимости объявлять privateконструкторы копирования и operator=делать класс не копируемым. Если вы не хотите, чтобы объекты класса копировались, вы просто не пишете функцию для копирования.
  • Там нет необходимости для swapфункций. (Если вы не пишете процедуру сортировки.)
  • Нет необходимости в ссылочных значениях в стиле C ++ 0x.
  • Нет необходимости в (N) RVO.
  • Нет проблем с нарезкой.
  • Компилятору проще определить расположение объектов, поскольку ссылки имеют фиксированный размер.

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

Java решила использовать недетерминированный сборщик мусора.

Разве GC не может быть детерминированным?

Да, оно может. Например, реализация Python на C использует подсчет ссылок. А позже добавили трассировку GC для обработки циклического мусора, где сбой refcounts.

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

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

Но как насчет C ++ / CLI "стековых объектов"?

Они просто синтаксический сахар дляDispose ( оригинальная ссылка ), очень похожий на C # using. Тем не менее, это не решает общую проблему детерминированного уничтожения, потому что вы можете создать анонимного, gcnew FileStream("filename.ext")а C ++ / CLI не удалит его автоматически.

dan04
источник
3
Также хорошие ссылки (особенно первая, которая очень актуальна для этой дискуссии) .
BlueRaja - Дэнни Пфлугхофт
Это usingутверждение прекрасно справляется со многими проблемами, связанными с очисткой, но многие другие остаются. Я бы предположил, что правильным подходом к языку и структуре будет декларативное различие между хранилищами, которые «владеют» ссылочными ссылками, и IDisposableтеми, на которые нет ссылок ; перезапись или отказ от места хранения, которому принадлежит ссылка, IDisposableдолжны ликвидировать цель в отсутствие директивы об обратном.
суперкат
1
«Не нужно копировать конструкторы» звучит хорошо, но на практике терпит неудачу. java.util.Date и Calendar, пожалуй, самые печально известные примеры. Нет ничего прекраснее, чем new Date(oldDate.getTime()).
Кевин Клайн
2
iow RAII не был «заброшен», он просто не существовал, чтобы быть заброшенным :) Что касается копирования конструкторов, они мне никогда не нравились, слишком легко ошибиться, они являются постоянным источником головной боли, когда где-то глубоко внутри кого-то (еще) забыл сделать глубокую копию, в результате чего ресурсы были разделены между копиями, что не должно быть.
августа
20

Java7 представила нечто похожее на C # using: оператор try-with-resources

tryзаявление , которое объявляет один или несколько ресурсов. Ресурс как объект , который должен быть закрыт после того, как программа закончит с ним. tryЗаявление -с-ресурсов гарантирует , что каждый ресурс закрыт в конце заявления. Любой объект, который реализует java.lang.AutoCloseable, который включает все объекты, которые реализуют java.io.Closeable, может использоваться в качестве ресурса ...

Так что я думаю, что они либо сознательно не решили не применять RAII, либо тем временем передумали.

Патрик
источник
Интересно, но похоже, что это работает только с объектами, которые реализуют java.lang.AutoCloseable. Вероятно, не имеет большого значения, но мне не нравится, как это выглядит несколько ограниченным. Может быть, у меня есть какой-то другой объект, который должен вызываться автоматически, но это очень семантически странно, чтобы заставить его реализовать AutoCloseable...
FrustratedWithFormsDesigner
9
@ Патрик: Э, так? usingэто не то же самое, что RAII - в одном случае вызывающий абонент беспокоится об утилизации ресурсов, а в другом - вызываемый.
BlueRaja - Дэнни Пфлюгофт
1
+1 Я не знал о попытке с ресурсами; это должно быть полезно при сбрасывании большего количества образцов.
jprete
3
-1 для using/ try-with-resources не совпадает с RAII.
Шон Макмиллан
4
@ Шон: Согласен. usingи это похоже далеко не RAII.
DeadMG
18

Java намеренно не имеет основанных на стеке объектов (также называемых объектами-значениями). Это необходимо для автоматического уничтожения объекта в конце метода.

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

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


Почему в Java нет стековых объектов?

(Кроме примитивов ..)

Потому что основанные на стеке объекты имеют другую семантику, чем ссылки на основе кучи. Представьте себе следующий код на C ++; Что это делает?

return myObject;
  • Если myObjectэто локальный стековый объект, вызывается конструктор копирования (если результат назначен чему-либо).
  • Если myObjectэто локальный стековый объект, и мы возвращаем ссылку, результат не определен.
  • Если myObjectэто член / глобальный объект, вызывается конструктор копирования (если результат назначен чему-либо).
  • Если myObjectэто член / глобальный объект, и мы возвращаем ссылку, ссылка возвращается.
  • Если myObjectуказатель на локальный стековый объект, результат не определен.
  • Если myObjectуказатель на член / глобальный объект, этот указатель возвращается.
  • Если myObjectуказатель на объект на основе кучи, этот указатель возвращается.

Что же делает код в Java?

return myObject;
  • Ссылка на myObjectвозвращается. Не имеет значения, является ли переменная локальной, членской или глобальной; и нет никаких основанных на стеке объектов или случаев указателя, о которых нужно беспокоиться.

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

BlueRaja - Дэнни Пфлугхофт
источник
6
Я не знаю, что вы подразумеваете под "нет смысла в RAII" ... Я думаю, что вы имеете в виду "нет возможности предоставить RAII в Java" ... RAII не зависит от какого-либо языка ... это не стать "бессмысленным", потому что 1 конкретный язык не обеспечивает его
JoelFan
4
Это не веская причина. Объект не должен фактически жить в стеке, чтобы использовать основанный на стеке RAII. Если существует такая вещь, как «уникальная ссылка», деструктор может быть запущен, как только он выходит из области видимости. Посмотрите, например, как это работает с языком программирования D: d-programming-language.org/exception-safe.html
Неманья Трифунович
3
@Nemanja: объект не должен жить в стеке, чтобы иметь основанную на стеке семантику, и я никогда не говорил, что это так. Но это не проблема; проблема, как я уже говорил, заключается в семантике на основе стека.
BlueRaja - Дэнни Пфлюгофт
4
@Aaronaught: дьявол в "почти всегда" и "большую часть времени". Если вы не закроете свое соединение с БД и не оставите его на GC для запуска финализатора, оно будет прекрасно работать с вашими юнит-тестами и серьезно прервется при развертывании в рабочей среде. Детерминированная очистка важна независимо от языка.
Неманя Трифунович
8
@NemanjaTrifunovic: Почему вы тестируете модуль при подключении к базе данных? Это не совсем юнит тест. Нет, извините, я не покупаю это. В любом случае вам не следует создавать соединения с БД повсеместно, вы должны передавать их через конструкторы или свойства, а это означает, что вам не нужна стековая семантика автоматического уничтожения. Очень немногие объекты, которые зависят от соединения с базой данных, должны на самом деле владеть им. Если недетерминированная очистка кусает вас так часто, так сложно, то это из-за плохого дизайна приложений, а не плохого языкового дизайна.
Аарона
17

Ваше описание дыр usingнеполное. Рассмотрим следующую проблему:

interface Bar {
    ...
}
class Foo : Bar, IDisposable {
    ...
}

Bar b = new Foo();

// Where's the Dispose?

На мой взгляд, отсутствие RAII и GC было плохой идеей. Когда дело доходит до закрытия файлов в Java, это malloc()и free()там.

DeadMG
источник
2
Я согласен, что RAII это колени пчел. Но этот usingпункт - большой шаг вперед для C # по сравнению с Java. Он допускает детерминированное разрушение и, следовательно, корректирует управление ресурсами (это не так хорошо, как RAII, как вы должны помнить, чтобы сделать это, но это определенно хорошая идея).
Мартин Йорк,
8
«Когда дело доходит до закрытия файлов в Java, это malloc () и free () там». - Абсолютно.
Конрад Рудольф
9
@KonradRudolph: Это хуже, чем malloc и бесплатно. По крайней мере, в C у вас нет исключений.
Неманя Трифунович
1
@Nemanja: Давайте будем честными, вы можете free()в finally.
DeadMG
4
@Loki: проблема базового класса гораздо важнее проблемы. Например, оригинал IEnumerableне наследовал от IDisposable, и было множество специальных итераторов, которые никогда не могли быть реализованы в результате.
DeadMG
14

Я довольно старый. Я был там и видел это, и много раз ударил головой об этом.

Я был на конференции в Херсли-Парке, где ребята из 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.

gbjbaanb
источник
ИМХО, такие вещи, как «использование» блоков, являются правильным подходом для детерминированной очистки, но также необходимо еще несколько вещей: (1) средство обеспечения удаления объектов, если их деструкторы выдают исключение; (2) средство автоматической генерации рутинного метода для вызова Disposeвсех полей, помеченных usingдирективой, и указания, IDisposable.Disposeдолжен ли он автоматически вызываться; (3) директива, аналогичная using, но которая будет вызываться только Disposeв случае исключения; (4) изменение, IDisposableкоторое будет принимать Exceptionпараметр, и ...
суперкат
... который будет использоваться автоматически usingпри необходимости; параметр был бы, nullесли usingблок вышел нормально, или иначе указал бы, какое исключение ожидало, если оно вышло через исключение. Если бы такие вещи существовали, было бы намного проще эффективно управлять ресурсами и избежать утечек.
суперкат
11

Брюс Экель, автор книг «Мышление в Java» и «Мышление в C ++» и член Комитета по стандартам C ++, считает, что во многих областях (не только в RAII) Гослинг и команда Java не домашнее задание.

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

Это также обмануло разработчиков Java, которые недостаточно хорошо понимали C ++. Например, они думали, что перегрузка операторов слишком сложна для программистов для правильного использования. Что в принципе верно для C ++, потому что C ++ имеет как распределение стека, так и распределение кучи, и вы должны перегружать свои операторы для обработки всех ситуаций, а не для утечек памяти. Действительно сложно. Java, однако, имеет единый механизм выделения памяти и сборщик мусора, что делает перегрузку операторов тривиальной - как было показано в C # (но уже было показано в Python, который предшествовал Java). Но в течение многих лет отчасти команда Java работала так: «Перегрузка оператора слишком сложна». Это и многие другие решения, когда кто-то явно не

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

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

Список продолжается до такой степени, что это просто утомительно ...

Gnawme
источник
5
Это звучит как ответ Java против C ++, а не на RAII. Я думаю, что C ++ и Java - это разные языки, каждый со своими сильными и слабыми сторонами. Также дизайнеры C ++ не делали свою домашнюю работу во многих областях (принцип KISS не применялся, простой механизм импорта отсутствующих классов и т. Д.). Но в центре внимания был вопрос RAII: в Java этого нет, и вам придется программировать его вручную.
Джорджио
4
@ Джорджио: Суть статьи в том, что Java, похоже, пропустил лодку по ряду вопросов, некоторые из которых имеют непосредственное отношение к RAII. Относительно C ++ и его влияния на Java, Экельс отмечает: «Вы должны иметь в виду основное дизайнерское решение, от которого зависело все в C ++: совместимость с C. Это было огромным ограничением и всегда было сильной стороной C ++ ... и его Это также обмануло разработчиков Java, которые недостаточно хорошо понимали C ++ ». Дизайн C ++ напрямую влиял на Java, а у C # была возможность учиться у обоих. (Так ли это - другой вопрос.)
Gnawme
2
@Giorgio Изучение существующих языков в определенной парадигме и языковой семье действительно является частью домашней работы, необходимой для развития нового языка. Это один из примеров, где они просто пахнули Java. У них был C ++ и Smalltalk, чтобы посмотреть. У C ++ не было Java, чтобы посмотреть, когда он был разработан.
Джереми
1
@Gnawme: «Кажется, Java пропустил лодку по ряду вопросов, некоторые из которых имеют непосредственное отношение к RAII»: можете ли вы упомянуть эти проблемы? В опубликованной вами статье не упоминается RAII.
Джорджио
2
@ Джорджио Конечно, с момента разработки C ++ появились инновации, которые учитывают многие функции, которых вам там не хватает. Есть ли какие-либо из этих функций, которые они должны были найти, глядя на языки, созданные до разработки C ++? Это та домашняя работа, о которой мы говорим с Java - у них нет причин не рассматривать каждую особенность C ++ в развитии Java. Некоторым нравится множественное наследование, которое они намеренно исключили - другим, например, RAII, они, кажется, упустили из виду.
Джереми
10

Лучшая причина гораздо проще, чем большинство ответов здесь.

Вы не можете передавать выделенные объекты стека другим потокам.

Остановись и подумай об этом. Продолжайте думать .... Теперь в C ++ не было потоков, когда все так увлеклись RAII. Даже Erlang (отдельные кучи на поток) становится неприглядным, когда вы передаете слишком много объектов. C ++ получил модель памяти только в C ++ 2011; теперь вы можете почти рассуждать о параллелизме в C ++, не обращаясь к «документации» вашего компилятора.

Java была разработана с (почти) первого дня для нескольких потоков.

У меня все еще есть моя старая копия "языка программирования C ++", где Страуструп уверяет меня, что мне не понадобятся потоки.

Вторая болезненная причина - избегать нарезки.

Тим Виллискрофт
источник
1
Java, разработанная для нескольких потоков, также объясняет, почему GC не основан на подсчете ссылок.
dan04
4
@NemanjaTrifunovic: Вы не можете сравнивать C ++ / CLI с Java или C #, это было разработано почти специально для взаимодействия с неуправляемым кодом C / C ++; это больше похоже на неуправляемый язык, который дает доступ к .NET Framework, а не наоборот.
Ааронаут
3
@NemanjaTrifunovic: Да, C ++ / CLI - один из примеров того, как это можно сделать способом, совершенно не подходящим для обычных приложений . Это полезно только для взаимодействия C / C ++. Мало того, что обычные разработчики не должны быть обременены абсолютно неуместным решением «стек или куча», но если вы когда-нибудь попытаетесь его реорганизовать, то легко создать случайно ошибку нулевого указателя / ссылки и / или утечку памяти. Извините, но я должен задаться вопросом, программировали ли вы когда-либо на Java или C #, потому что я не думаю, что кому-то, кто на самом деле хотел бы семантику, используемую в C ++ / CLI.
Aaronaught
2
@Aaronaught: я программировал как на Java (немного), так и на C # (много), и мой текущий проект в значительной степени полностью на C #. Поверьте, я знаю, о чем говорю, и это не имеет никакого отношения к «стеку и куче» - оно имеет отношение к тому, чтобы все ваши ресурсы были освобождены, как только они вам не понадобятся. Автоматически. Если нет - вы будете попасть в беду.
Неманя Трифунович
4
@NemanjaTrifunovic: Это здорово, действительно здорово, но и C #, и C ++ / CLI требуют, чтобы вы явно указывали, когда вы этого хотите, они просто используют другой синтаксис. Никто не оспаривает существенный момент, о котором вы сейчас говорите («ресурсы высвобождаются, как только они вам не нужны»), но вы делаете гигантский логический скачок к «все управляемые языки должны иметь автоматический, но только -сортировка детерминированного удаления на основе стека вызовов ". Это просто не держит воды.
Aaronaught
5

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

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

Имре
источник
7
«Философия« дай программистам шанс раскрутить свою собственную »» прекрасно работает, пока вам не нужно объединять библиотеки, написанные программистами, каждый из которых катал свои собственные классы строк и умные указатели.
dan04
@ dan04, так что управляемые языки, которые дают вам заранее определенные строковые классы, затем позволяют вам обезопасить их от патчей, что является поводом для катастрофы, если вы тот тип парня, который не может справиться с другой собственной прокрученной строкой класс.
gbjbaanb
-1

Вы уже вызвали грубый эквивалент этого в C # с помощью Disposeметода. Ява также имеет finalize. ПРИМЕЧАНИЕ: я понимаю, что финализация Java не является детерминированной и отличается от нее Dispose, я просто указываю, что у них обоих есть метод очистки ресурсов наряду с GC.

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

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

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

maple_shaft
источник
12
Прежде всего, «финализация» Java недетерминирована ... она не эквивалентна «утилизации» C # или деструкторам C ++… также, C ++ также имеет сборщик мусора, если вы используете .NET
JoelFan
2
@DeadMG: Проблема в том, что вы можете не быть идиотом, а другим парнем, который только что покинул компанию (и написал код, который вы сейчас поддерживаете).
Кевин
7
Этот парень собирается писать дерьмовый код, что бы вы ни делали. Вы не можете взять плохого программиста и заставить его написать хороший код. Свисающие указатели - наименьшее из моих опасений при работе с идиотами. Хорошие стандарты кодирования используют интеллектуальные указатели для памяти, которая должна отслеживаться, поэтому интеллектуальное управление должно показывать, как безопасно отменить выделение и доступ к памяти.
DeadMG
3
Что сказал DeadMG. В C ++ есть много плохих вещей. Но RAII далеко не один из них. Фактически, отсутствие Java и .NET для надлежащего учета управления ресурсами (потому что память является единственным ресурсом, верно?) Является одной из их самых больших проблем.
Конрад Рудольф
8
Финализатор, на мой взгляд, является мудрым дизайном. Поскольку вы навязываете правильное использование объекта от дизайнера пользователю объекта (не с точки зрения управления памятью, а управления ресурсами). В C ++ ответственность за правильное управление ресурсами лежит на дизайнере классов (выполняется только один раз). В Java ответственность за правильное управление ресурсами лежит на пользователе класса, и это должно выполняться каждый раз, когда используется класс. stackoverflow.com/questions/161177/…
Мартин Йорк