Чи свідомі розробники Java відмовилися від RAII?


82

Як давній програміст C #, нещодавно я прийшов дізнатися більше про переваги придбання ресурсів - це ініціалізація (RAII). Зокрема, я виявив, що ідіома C #:

using (var dbConn = new DbConnection(connStr)) {
    // do stuff with dbConn
}

має еквівалент С ++:

{
    DbConnection dbConn(connStr);
    // do stuff with dbConn
}

Це означає, що запам'ятовувати, щоб укласти використання ресурсів, таких DbConnectionяк usingблок, непотрібне в C ++! Це здається головною перевагою 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?)


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, які хочуть забезпечити безпеку пам’яті та чітко виражену поведінку, навіть якщо ми всі діємо як ідіоти. Для цього потрібен GC для всього і ніякого способу вручну знищити об'єкт (і весь код Java в існуванні покладається принаймні на колишній), так що або ви робите GC детермінованим, або вам не пощастило.

26
@delan. Я б не називав C ++ розумними вказівниками manualуправління пам’яттю. Вони більше схожі на детермінований сміттєзбірник, що контролюється дрібним зерном. Якщо правильно використовувати розумні покажчики - це коліна бджіл.
Мартін Йорк

10
@LokiAstari: Ну, я б сказав, що вони трохи менш автоматичні, ніж повний GC (ви повинні подумати про те, якого розуму ви хочете насправді), а їх реалізація як бібліотека потребує необроблених покажчиків (а отже, і управління ручною пам’яттю), щоб будувати . Крім того, я не знаю жодного розумного вказівника, ніж автоматично обробляє циклічні посилання, що є суворою вимогою до збирання сміття в моїх книгах. Розумні покажчики, безумовно, неймовірно круті та корисні, але вам доведеться зіткнутися, вони не можуть дати певних гарантій (чи вважаєте ви їх корисними чи ні) повністю та виключно мови GC'd.

11
@delan: Я з цим не погоджуюся. Я думаю, що вони є більш автоматичними, ніж GC, оскільки вони детерміновані. ГАРАЗД. Щоб бути ефективними, вам потрібно переконатися, що ви використовуєте правильний (я дам вам це). std :: слабкий_пт прекрасно справляється з циклами. Цикли завжди вимальовуються, але насправді це насправді навряд чи є проблемою, тому що базовий об'єкт, як правило, заснований на стеці, і коли це відбудеться, він приведе в дію решту. Для рідкісних випадків це може бути проблема std :: слабкий_птр.
Мартін Йорк

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 - Danny Pflughoeft

8
@ Петер Тейлор - правильно. Але я вважаю, що недетермінований деструктор C # коштує дуже мало, тому що ви не можете покластися на нього для управління будь-яким обмеженим ресурсом. Тож, на мій погляд, можливо, було б краще використовувати ~синтаксис як синтаксичний цукор дляIDisposable.Dispose()
Larry 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 бути детермінованим?

Так, це може. Наприклад, C-реалізація Python використовує підрахунок посилань. А пізніше додано відстежувати GC для обробки циклічного сміття там, де знижки не вдається.

Але перерахунок рахунків жахливо неефективний. Багато оновлених процесорних циклів витрачалось на оновлення підрахунків. Ще гірше в багатопотоковому середовищі (на зразок виду Java, призначеного для), де ці оновлення потрібно синхронізувати. Набагато краще використовувати нульовий збірник сміття, поки вам не потрібно перейти на інший.

Можна сказати, що Java вирішила оптимізувати загальний випадок (пам'ять) за рахунок таких, що не є запальними ресурсами, як файли та сокети. Сьогодні, з огляду на прийняття RAII в C ++, це може здатися неправильним вибором. Але пам’ятайте, що значна частина цільової аудиторії для Java були програмістами C (або "C з класами"), які використовувались для явного закриття цих речей.

А як щодо C ++ / CLI "стек об'єктів"?

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


3
Також приємні посилання (особливо перше, що дуже актуально для цієї дискусії) .
BlueRaja - Danny Pflughoeft

usingОператор обробляє багато проблем очистки пов'язаних красиво, але багато інших залишаються. Я б припустив, що правильним підходом до мови та фреймворку було б декларативно розрізняти місця зберігання, які "володіють" посиланням, IDisposableвід тих, які не мають; перезапис або відмова від місця зберігання, на яке належить посилання, IDisposableповинно розпоряджатися ціллю, якщо директива не суперечить.
supercat

1
"Не потрібно конструкторів копій" звучить приємно, але погано виходить з ладу. java.util.Date та Календар - це, мабуть, найвідоміші приклади. Нічого милішого new Date(oldDate.getTime()).
Кевін Клайн

2
iow RAII не був "покинутим", він просто не існував, щоб його покинути :) Що стосується копіювання конструкторів, я ніколи їх не любив, занадто легко помилитися, вони постійне джерело головних болів, коли десь в глибині когось (інше) забув зробити глибоку копію, внаслідок чого ресурси поділялися між копіями, яких не повинно бути.
1313

20

Java7 представив щось подібне до заявки C # using: Пробні ресурси

tryзаяву , яке оголошує один або кілька ресурсів. Ресурс як об'єкт , який повинен бути закритий після того, як програма закінчить з ним. Заява- trywith-Resources забезпечує закриття кожного ресурсу в кінці оператора. Будь-який об'єкт, який реалізує java.lang.AutoCloseable, який включає всі об'єкти, які реалізує java.io.Closeable, може бути використаний як ресурс ...

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


Цікаво, але схоже, що це працює лише з об’єктами, які реалізують java.lang.AutoCloseable. Напевно, це не велика справа, але мені не подобається, як це відчувається дещо стримано. Можливо, у мене є якийсь інший об’єкт, який повинен бути відновлений автоматично, але це дуже семантично дивно, щоб зробити його реалізацією AutoCloseable...
FrustratedWithFormsDesigner

9
@Patrick: Е, так? usingне є тим самим, що RAII - в одному випадку абонент переживає за розпорядження ресурсами, в іншому випадку він звертається з ним.
BlueRaja - Danny Pflughoeft

1
+1 я не знав про тест-ресурси; це повинно бути корисним для скидання більшої кількості котельних плит
jprete

3
-1 для using/ спробуйте з ресурсами, не такі, як RAII.
Шон Макміллан

4
@Sean: Погоджено. usingі це не так вже ніде поблизу RAII.
DeadMG

18

У Java навмисно немає об'єктів на основі стека (він же значень-об'єктів). Вони необхідні для автоматичного знищення об'єкта в кінці методу.

Через це і той факт, що Java збирається сміттям, детермінована доопрацювання більш-менш неможлива (наприклад, що робити, якщо на мій "локальний" об'єкт посилається десь інше? Тоді, коли метод закінчиться, ми не хочемо, щоб він був знищений ) .

Однак це добре в більшості з нас, тому що майже ніколи не виникає необхідності в детермінованій доопрацюванні, крім випадків взаємодії з рідними (C ++) ресурсами!


Чому у Java немає об'єктів на основі стека?

(Крім примітивів ..)

Тому що об'єкти на основі стека мають іншу семантику, ніж посилання на основі купи. Уявіть наступний код у С ++; що воно робить?

return myObject;
  • Якщо myObjectце локальний об'єкт на основі стека, викликається конструктор копіювання (якщо результат присвоюється чомусь).
  • Якщо myObjectце локальний об'єкт на основі стека, і ми повертаємо посилання, результат не визначений.
  • Якщо myObjectце член / глобальний об'єкт, викликається конструктор копіювання (якщо результат присвоюється чомусь).
  • Якщо myObjectє членом / глобальним об'єктом, а ми повертаємо посилання, повертається посилання.
  • Якщо myObjectвказівник на локальний об'єкт на основі стека, результат не визначений.
  • Якщо myObjectвказівник на член / глобальний об'єкт, цей покажчик повертається.
  • Якщо myObjectвказівник на об'єкт на основі купи, цей покажчик повертається.

Тепер, що робить той же код у Java?

return myObject;
  • Посилання на myObjectповертається. Не має значення, чи змінна локальна, членська чи глобальна; і немає жодних предметів на основі стека або випадкових покажчиків, яких слід турбувати.

Сказане вище показує, чому об’єкти на основі стека є дуже частою причиною помилок програмування в C ++. Через це дизайнери Java вивели їх; і без них немає сенсу використовувати RAII на Java.


6
Я не знаю, що ви маєте на увазі під "RAII немає сенсу" ... Я думаю, ви маєте на увазі "немає можливості надати RAII в Java" ... RAII не залежить від будь-якої мови ... це не стають "безглуздими", оскільки 1 конкретна мова не надає цього
JoelFan

4
Це не вагома причина. Об'єкт не повинен насправді жити на стеці, щоб використовувати RAII на основі стека. Якщо є така річ, як "унікальна довідка", деструктора можна звільнити, як тільки він вийде за межі сфери застосування. Дивіться, наприклад, як це працює з мовою програмування D: d-programming-language.org/exception-safe.html
Nemanja Trifunovic

3
@Nemanja: Об'єкт не повинен жити на стеці, щоб мати семантику на основі стека, і я ніколи не казав, що це робиться. Але це не проблема; Проблема, як я вже згадувала, полягає в самій семантиці на основі стека.
BlueRaja - Danny Pflughoeft

4
@Aaronaught: Чорт перебуває у «майже завжди» і «більшій частині часу». Якщо ви не закриєте db-з'єднання і не залишите його в GC, щоб запустити фіналізатор, він буде добре працювати з вашими одиничними тестами і серйозно зламатися при розгортанні у виробництві. Детермінований спосіб очищення важливий незалежно від мови.
Неманя Трифунович

8
@NemanjaTrifunovic: Чому ви проводите тестування на підключенні до бази даних? Це насправді не одиничне випробування. Ні, вибачте, я не купую. У будь-якому разі не слід створювати з'єднання БД скрізь, ви повинні передавати їх через конструктори або властивості, а це означає, що вам не потрібно семантики автоматичного руйнування, схожої на стек. Дуже мало об’єктів, які залежать від з'єднання з базою даних, насправді повинні володіти ним. Якщо недетерміновані засоби очищення вас кусають так часто, це важко, то це через поганий дизайн програми, а не поганий дизайн мови.
Aaronaught

17

Опис ваших отворів usingнеповний. Розглянемо таку проблему:

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

Bar b = new Foo();

// Where's the Dispose?

На мою думку, не мати RAII та GC була поганою ідеєю. Якщо справа доходить до закриття файлів на Java, вона там malloc()і free()там.


2
Я згоден, що RAII - це коліна бджіл. Але ця usingпропозиція - це великий крок вперед для C # над Java. Це дозволяє детерміновані знищення і, таким чином, коригувати управління ресурсами (це не так добре, як RAII, як вам потрібно пам'ятати, щоб це зробити, але, безумовно, хороша ідея).
Мартін Йорк

8
"Що стосується закриття файлів на Java, то там є malloc () та free ()." - Абсолютно.
Конрад Рудольф

9
@KonradRudolph: Це гірше, ніж malloc та вільний. Принаймні у С у вас немає винятків.
Неманья Трифунович

1
@Nemanja: Будемо чесні, ви можете free()в finally.
DeadMG

4
@Loki: Проблема базового класу набагато важливіша як проблема. Наприклад, оригінал IEnumerableне успадкував IDisposable, і було купу спеціальних ітераторів, які ніколи не могли бути реалізовані.
DeadMG

14

Я досить стара. Я був там і бачив це і багато разів бив головою про це.

Я був на конференції в парку Херслі, де хлопці IBM розповідали нам, наскільки чудовою була ця абсолютно нова мова Java, лише хтось запитав ... чому не існує деструктора для цих об’єктів. Він не мав на увазі те, що ми знаємо як деструктора в C ++, але не було і фіналізатора (або у нього були фіналізатори, але вони в основному не працювали). Це повернення назад, і ми вирішили, що Java в цій точці була трохи іграшковою мовою.

тепер вони додали Finalisers до мовної специфікації, і Java побачила деяке прийняття.

Звичайно, пізніше всім сказали не ставити фіналізаторів на свої об'єкти, оскільки це надзвичайно сповільнило ГК. (як це потрібно було не тільки заблокувати купу, але перемістити об'єкти, що підлягають завершенню, до тимчасової області, оскільки ці методи не можна було викликати, оскільки GC призупинила роботу програми. Замість цього вони будуть викликані безпосередньо перед наступним Цикл GC) (і ще гірше, іноді фіналізатор ніколи не зателефонував би, коли програма закривалася. Уявіть, ніколи не закриваєте обробку файлів)

Тоді у нас був C #, і я пам’ятаю дискусійний форум на MSDN, де нам сказали, наскільки чудова ця нова мова C #. Хтось запитав, чому немає детермінованої доопрацювання, і хлопці з MS розповіли нам, як нам такі речі не потрібні, потім сказали нам, що нам потрібно змінити спосіб розробки програм, потім сказали нам, наскільки дивовижним є GC та як усі наші старі програми сміття і ніколи не працювало через всі кругові посилання. Тоді вони піддалися тиску і сказали нам, що вони додали цю схему ідентифікації до специфікації, яку ми могли б використати. Я подумав, що в цей час ми повернулися до ручного управління пам'яттю в додатках C #.

Звичайно, пізніше хлопці з MS виявили, що все, що вони нам сказали, це ... ну, вони зробили IDispose трохи більше, ніж просто стандартний інтерфейс, а пізніше додали оператор use. W00t! Вони зрозуміли, що детермінованої доопрацювання все-таки щось не вистачає в мові. Звичайно, ви все ще повинні пам’ятати, щоб розмістити його скрізь, тому його все-таки трохи посібник, але це краще.

То чому вони зробили це, коли їм могло було використовувати семантику стилю автоматично, розміщену на кожному блоці області з самого початку? Можливо, ефективність, але мені подобається думати, що вони просто не усвідомлювали. Як і в кінцевому підсумку вони зрозуміли, що вам все ще потрібні розумні покажчики в .NET (google SafeHandle), вони думали, що GC дійсно вирішить усі проблеми. Вони забули, що об'єкт - це не просто пам'ять, а GC призначений насамперед для управління пам'яттю. вони захопилися думкою, що GC впорається з цим, і забули, що ти туди вкладаєш інші речі, об’єкт - це не просто плівка пам'яті, яка не має значення, якщо ти її не видалиш на деякий час.

Але я також думаю, що відсутність методу доопрацювання в оригінальній Java було трохи більше - це те, що створені вами об'єкти стосувалися пам’яті, і якщо ви хочете видалити щось інше (наприклад, обробку БД або сокет чи будь-що інше) ) тоді від вас очікували зробити це вручну .

Пам'ятайте, що Java була розроблена для вбудованих середовищ, де люди звикли писати код C з великою кількістю ручних виділень, тому не мати автоматичного безкоштовного не було великою проблемою - вони ніколи цього не робили, тому навіщо вам це потрібно на Java? Проблема не мала нічого спільного з потоками або стеком / купами, можливо, саме там було трохи полегшити розподіл пам’яті (і, отже, делокацію). Загалом, операція спробувати / нарешті, мабуть, є кращим місцем для обробки ресурсів, що не належать до пам'яті.

Отже, IMHO, спосіб .NET просто скопіював найбільший недолік Java - це її найбільша слабкість. .NET повинен був бути кращим C ++, а не кращою Java.


ІМХО, такі речі, як "використання" блоків, - це правильний підхід для детермінованого очищення, але також потрібно ще декілька речей: (1) засіб забезпечення об'єктів утилізації, якщо їх руйнівники кидають виняток; (2) засіб автоматичного генерування рутинного методу для виклику Disposeвсіх полів, позначених usingдирективою, та визначення того, чи IDisposable.Disposeслід автоматично його викликати; (3) директива, аналогічна using, але яка закликала б лише Disposeу випадку винятку; (4) варіант, IDisposableякий би прийняв Exceptionпараметр, і ...
supercat

... які автоматично використовуються, usingякщо це доречно; параметр був би, nullякщо usingблок вийшов нормально, або ж інакше вказував би, який виняток очікується, якщо він вийшов через виняток. Якби такі речі існували, було б набагато простіше ефективно керувати ресурсами та уникати витоків.
supercat

11

Брюс Еккель, автор книг "Мислення на Яві" та "Мислення на C ++" та член Комітету зі стандартів C ++, вважає, що в багатьох областях (не лише RAII) Гослінг та команда Java не зробили своїх домашнє завдання.

... Щоб зрозуміти, як мова може бути і неприємною, і складною, і одночасно добре розробленою, ви повинні пам’ятати про первинне дизайнерське рішення, на якому опинилося все на C ++: сумісність з C. Stroustrup вирішила - і правильно так , може здатися, що спосіб змусити маси програмістів C переміщуватися до об'єктів полягав у тому, щоб зробити хід прозорим: дозволити їм складати свій C код незмінним під C ++. Це було величезним обмеженням і завжди було найбільшою силою C ++ ... і його перевагою. Саме це зробило C ++ настільки ж успішним і настільки ж складним, як і воно.

Це також обдурило дизайнерів Java, які недостатньо добре зрозуміли C ++. Наприклад, вони вважали, що перевантаження операторів занадто важке для правильного використання програмістів. Що в основному вірно в C ++, оскільки C ++ має як розподіл стеків, так і розподіл купи, і ви повинні перевантажувати своїх операторів для обробки всіх ситуацій і не спричиняти витоків пам'яті. Справді важко. Однак Java має єдиний механізм розподілу сховища та збирач сміття, що робить перевантаження оператора тривіальним - як це було показано на C # (але це вже було показано в Python, який передував Java). Але впродовж багатьох років команда Java частково відзначала: "Перевантаження оператора занадто складна". Це та багато інших рішень, коли хтось явно не робив '

Існує маса інших прикладів. Примітиви "повинні були бути включені для ефективності". Правильна відповідь - залишатися вірним "все є об'єктом" та забезпечити пастку для здійснення заходів нижчого рівня, коли необхідна ефективність (це також дозволило б технологіям гарячої точки прозоро зробити речі більш ефективними, оскільки вони в кінцевому підсумку мають). О, і той факт, що ви не можете використовувати процесор з плаваючою комою безпосередньо для обчислення трансцендентальних функцій (це робиться замість програмного забезпечення). Я писав про подібні питання настільки, наскільки я витримую, і відповідь, яку я чую, завжди була якоюсь тавтологічною відповіддю на те, що "це шлях на Яві".

Коли я писав про те, як погано розроблені дженерики, я отримав таку ж відповідь, а також "ми повинні бути сумісні з попередніми (поганими) рішеннями, прийнятими на Яві". Останнім часом все більше і більше людей набувають достатнього досвіду роботи з Generics, щоб побачити, що їх дійсно дуже важко використовувати - дійсно, шаблони C ++ є набагато потужнішими та послідовнішими (і набагато простіше використовувати зараз, коли повідомлення про помилки компілятора є терпимими). Люди навіть серйозно сприймають реіфікацію - те, що може бути корисним, але не вкладе стільки вм'ятини в дизайн, який покалічений самостійними обмеженнями.

Список продовжується до того, що це просто нудно ...


5
Це звучить як відповідь Java проти C ++, а не фокусування на RAII. Я думаю, що C ++ та Java - це різні мови, кожна зі своїх сильних і слабких сторін. Також дизайнери C ++ не виконували домашніх завдань у багатьох сферах (принцип KISS не застосовується, простий механізм імпорту для класів відсутній тощо). Але в центрі уваги питання було RAII: цього не вистачає в Java, і ви повинні запрограмувати його вручну.
Джорджіо

4
@Giorgio: Суть статті полягає в тому, що 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
@ Giorgio Sure, з моменту розробки C ++ з'явилися нововведення, які пояснюють багато функцій, яких вам там бракує. Чи є якась із тих особливостей, яку вони мали б знайти, переглядаючи мови, створені до розробки C ++? Саме про таку домашню роботу ми говоримо з Java - немає жодної причини, щоб вони не враховували будь-яку функцію C ++ у розвитку Java. Деяким подобається багатократне успадкування, яке вони навмисно лишили, а інші, як RAII, начебто вони ігнорували.
Джеремі

10

Найкраща причина набагато простіша, ніж більшість відповідей тут.

Ви не можете передавати виділені об'єкти стека в інші потоки.

Зупиніться і подумайте над цим. Продовжуйте думати .... Тепер у C ++ не було тем, коли всі так захопилися RAII. Навіть Ерланг (окремі купи на нитку) стає примхливим, коли ви передаєте занадто багато предметів навколо. C ++ отримала модель пам'яті лише у C ++ 2011; тепер ви можете майже міркувати про сумісність у C ++, не звертаючись до "документації" вашого компілятора.

Java розроблялася з (майже) першого дня для декількох потоків.

У мене ще є стара копія "Мова програмування на C ++", де Stroustrup запевняє, що мені не потрібні теми.

Друга болюча причина - уникати нарізання.


1
Java, розроблена для декількох потоків, також пояснює, чому GC не заснований на підрахунку посилань.
dan04

4
@NemanjaTrifunovic: Ви не можете порівнювати C ++ / CLI з Java або C #, він був розроблений майже для прямої мети взаємодії з керованим кодом C / C ++; це більше схоже на некеровану мову, яка, можливо, дає доступ до .NET-бази, ніж навпаки.
Aaronaught

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 ++, де інші, як я, віддають перевагу більш пісочній мові, яка набагато чіткіша.


12
Перш за все, "фіналізація" Java не є детермінованою ... це не еквівалент C "'утилізації" або C ++' деструкторам ... також, C ++ також має сміттєзбірник, якщо ви використовуєте .NET
JoelFan

2
@DeadMG: Проблема полягає в тому, що ви, можливо, не ідіот, але може бути інший хлопець, який щойно покинув компанію (і який написав код, який ви зараз підтримуєте).
Кевін

7
Той хлопець збирається писати лайний код, що б ти не робив. Ви не можете взяти поганого програміста і змусити його написати хороший код. Покажчики, що розвішуються, є найменшим моїм занепокоєнням при роботі з ідіотами. Хороші стандарти кодування використовують смарт-покажчики для пам’яті, яку потрібно відслідковувати, тому розумне управління повинно дати зрозуміти, як безпечно розподілити та отримати доступ до пам’яті.
DeadMG

3
Що сказав DeadMG. Про C ++ є багато поганих речей. Але RAII не є одним з них на великій відстані. Насправді, відсутність Java та .NET для належного обліку управління ресурсами (адже пам'ять - єдиний ресурс, правда?) - одна з найбільших проблем.
Конрад Рудольф

8
На мою думку, фіналізатор - це мудрий дизайн. Оскільки ви змушуєте правильно використовувати об'єкт від дизайнера до користувача об'єкта (не з точки зору управління пам'яттю, а управління ресурсами). У програмі C ++ відповідальність дизайнера класів є за правильність управління ресурсами (зроблено лише один раз). У Java це відповідальність користувача класу за правильність управління ресурсами, і, таким чином, це потрібно робити кожен раз, коли клас, який ми використовували. stackoverflow.com/questions/161177/…
Мартін Йорк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.