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
}

つまりDbConnectionusingブロックのようにリソースの使用を囲むことを覚えておくことは、C ++では不要です。これは、C ++の大きな利点のようです。あなたはタイプのインスタンスメンバを持つクラスを考えるとき、これは、より説得力があるDbConnectionたとえば、

class Foo {
    DbConnection dbConn;

    // ...
}

C#では、Foo IDisposableを次のように実装する必要があります。

class Foo : IDisposable {
    DbConnection dbConn;

    public void Dispose()
    {       
        dbConn.Dispose();
    }
}

さらに悪い何、のすべてのユーザーがFoo囲む覚えておく必要があるだろうFoousingように、ブロック:

   using (var foo = new Foo()) {
       // do stuff with "foo"
   }

今、C#とそのJavaのルーツを見て、私は疑問に思っています... Javaの開発者は、ヒープを優先してスタックを放棄し、RAIIを放棄したときに放棄したものを完全に感謝しましたか?

(同様に、StroustrupはRAIIの重要性を完全に評価しましたか?)


5
リソースをC ++で囲まないことについて何を話しているのかわかりません。DBConnectionオブジェクトは、おそらくデストラクタ内のすべてのリソースを閉じます。
maple_shaft

16
@maple_shaft、まさに私のポイント!これが、この質問で対処しているC ++の利点です。C#では、リソースを "using"で囲む必要があります... C ++では必要ありません。
ジョエルファン

12
私の理解では、RAIIは戦略として、C ++コンパイラが高度なテンプレートを実際に使用するのに十分であり、Javaをはるかに超えて初めて理解されたということです。Javaが作成されたときに使用するために、実際に利用可能であったC ++はと、非常に原始的な、「Cクラスと」スタイルだったかもしれないあなたはラッキーだった場合は、基本的なテンプレート。
ショーンマクミラン

6
「私の戦略では、RAIIは戦略として、C ++コンパイラが高度なテンプレートを実際に使用するのに十分であり、Javaをはるかに超えて初めて理解されたということです。」-それは本当に正しくありません。コンストラクターとデストラクターは、初日から、テンプレートが広く使用されるずっと前、そしてJavaのかなり前に、C ++のコア機能でした。
ジムでテキサス州

8
@JimInTexas:Seanにはどこかに真実の基本的な種があると思います(ただし、テンプレートではなく例外が重要です)。コンストラクタ/デストラクタは最初から存在していましたが、RAIIの重要性と概念は当初(私が探している言葉は)実現していませんでした。RAII全体がどれほど重要であるかを理解するまでに、コンパイラがうまくいくのに数年と時間がかかりました。
マーティンヨーク

回答:


38

今、C#とそのJavaのルーツを見て、私は疑問に思っています... Javaの開発者は、ヒープを優先してスタックを放棄し、RAIIを放棄したときに放棄したものを完全に感謝しましたか?

(同様に、StroustrupはRAIIの重要性を完全に評価しましたか?)

GoslingがJavaの設計時にRAIIの重要性を理解していなかったと確信しています。彼のインタビューでは、ジェネリックと演算子のオーバーロードを除外する理由についてよく話しましたが、決定論的なデストラクタとRAIIについては言及しませんでした。

面白いことに、Stroustrupでさえ、設計時に決定論的なデストラクタの重要性を認識していませんでした。私は引用を見つけることができませんが、あなたが本当にそれに興味があるなら、あなたはここで彼のインタビューの中でそれを見つけることができます:http : //www.stroustrup.com/interviews.html


11
@maple_shaft:要するに、それは不可能です。確定的なガベージコレクション(一般的に不可能と思われ、いずれにしても過去10年のすべてのGC最適化を無効にする)を考案した場合を除き、スタック割り当てオブジェクトを導入する必要がありますが、ワーム:これらのオブジェクトには、重要な制限を設けたり、互換性のない型システムを大幅に変更したりしない限り、セマンティクス、サブタイピングによる「スライスの問題」(したがって多態性なし)、ポインターのダングリングが必要です。そして、それは私の頭上から外れています。

13
@DeadMG:手動のメモリ管理に戻ることをお勧めします。これは一般的なプログラミングに対する有効なアプローチであり、もちろん決定論的な破壊を可能にします。しかし、それはこの質問には答えません。GCのみの設定では、全員が馬鹿のように振る舞う場合でも、メモリの安全性と明確な動作を提供したいと考えています。そのためには、すべてにGCが必要であり、オブジェクトの破壊を手動で開始する方法はありません(そして、存在するすべての Javaコードは少なくとも前者に依存しています)。したがって、GCを決定論的にするか、運が悪くなります。

26
@delan。私はC ++スマートポインターのmanualメモリ管理を呼び出さないでしょう。それらは、決定論的な細粒度の制御可能なガベージコレクターに似ています。正しく使用すると、スマートポインターはミツバチの膝です。
マーティンヨーク

10
@LokiAstari:まあ、彼らはフルGCよりも少し自動ではない(実際にどの種類のスマートネスが必要かを考える必要があります)とライブラリとして実装するには、生のポインタが必要です。また、循環参照を自動的に処理する以外にスマートポインターを認識していません。これは、本のガベージコレクションの厳密な要件です。スマートポインターは確かに信じられないほどクールで便利ですが、完全にGC化された言語の(それらが役立つかどうかにかかわらず)保証を提供できないことに直面する必要があります。

11
@デラン:私はそこに反対する必要があります。確定的であるため、GCよりも自動化されていると思います。OK。効率的にするためには、正しいものを使用することを確認する必要があります(私はあなたにそれを与えます)。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を提供するスタックベースの「ハンドル」もありますか?
ジョエルファン

3
C ++ / CLIには、設計上の決定と制約の非常に異なるセットがあります。これらの決定のいくつかは、プログラマーにメモリ割り当てとパフォーマンスへの影響についてより多くの考えを要求できることを意味します:「自分自身を掛けるのに十分なロープを与える」全体のトレードオフ。また、C ++ / CLIコンパイラは、C#のコンパイラ(特に初期の世代)よりもかなり複雑だと思います。
ラリーオブライエン

5
+1これはこれまでのところ唯一の正しい答えです-Javaが(非プリミティブな)スタックベースのオブジェクトを意図的に持たないためです。
BlueRaja-ダニーPflughoeft

8
@ピーター・テイラー-そう。しかし、C#の非決定的デストラクタは、あらゆる種類の制約されたリソースの管理に頼ることができないため、ほとんど価値がありません。だから、私の意見では、~構文を使用して構文糖衣をする方が良かったかもしれませんIDisposable.Dispose()
ラリーオブライエン

3
@ラリー:同意します。C ++ / CLIはない使用~のためのシンタックスシュガーとしてIDisposable.Dispose()、それははるかに便利C#構文よりもです。
dan04

41

Javaは、C ++がまったく異なる言語であった1991〜1995年に開発されたことに留意してください。例外(RAIIが必要になった)とテンプレート(スマートポインターの実装が簡単になった)は、「新しい」機能です。ほとんどのC ++プログラマはCから来ており、手動のメモリ管理を行うことに慣れていました。

したがって、Javaの開発者がRAIIを放棄することを意図的に決定したとは思えません。ただし、Javaでは値のセマンティクスではなく参照のセマンティクスを優先することを意図的に決定しました。 決定論的破壊は、参照セマンティクス言語では実装が困難です。

では、なぜ値セマンティクスの代わりに参照セマンティクスを使用するのでしょうか?

それは言語をずっと単純にするからです。

  • 間の構文上の区別は必要ありませんFooし、Foo*あるいは間foo.barとはfoo->bar
  • すべての割り当てがポインターのコピーである場合、オーバーロードされた割り当ての必要はありません。
  • コピーコンストラクターは必要ありません。(たまに明示的なコピー機能clone()が必要になることがあります。多くのオブジェクトはコピーする必要がないだけです。たとえば、不変なオブジェクトは必要ありません。)
  • privateコピーコンストラクタを宣言したりoperator=、クラスをコピー不可にする必要はありません。クラスのオブジェクトをコピーしたくない場合は、コピーする関数を作成しないでください。
  • swap機能は必要ありません。(ソートルーチンを記述している場合を除きます。)
  • C ++ 0xスタイルの右辺値参照の必要はありません。
  • (N)RVOの必要はありません。
  • スライスの問題はありません。
  • 参照のサイズは固定されているため、コンパイラーはオブジェクトのレイアウトを簡単に決定できます。

参照セマンティクスの主な欠点は、すべてのオブジェクトが潜在的に複数の参照を持っている場合、それをいつ削除するかがわかりにくくなることです。ほとんどの場合、自動メモリ管理が必要です。

Javaは、非決定的ガベージコレクターの使用を選択しました。

GCは決定論的ではありませんか?

はい、できます。たとえば、PythonのC実装は参照カウントを使用します。また、後でrefcountsが失敗するサイクリックガベージを処理するトレースGCを追加しました。

しかし、リカウントは恐ろしく非効率的です。カウントの更新に多くのCPUサイクルが費やされました。さらに悪いことに、これらの更新を同期する必要があるマルチスレッド環境(Javaが設計されたようなもの)では。別のガベージコレクタに切り替える必要があるまでは、nullガベージコレクタを使用する方がはるかに優れています。

Javaは、ファイルやソケットなどの代替不可能なリソースを犠牲にして、一般的なケース(メモリ)を最適化することを選択したと言えます。今日、C ++でのRAIIの採用に照らして、これは間違った選択のように思えるかもしれません。しかし、Javaの対象読者の多くは、これらのことを明示的に閉じることに慣れていたC(または「C with classes」)プログラマーであったことを思い出してください。

しかし、C ++ / CLIの「スタックオブジェクト」はどうでしょうか。

C#によく似た(元のリンク)の構文糖衣Disposeです。ただし、匿名を作成でき、C ++ / CLIは自動破棄しないため、決定論的破壊の一般的な問題は解決しません。usinggcnew FileStream("filename.ext")


3
また、素晴らしいリンク(特に最初のリンク、これはこの議論に非常に関連しています)
BlueRaja-ダニーPflughoeft

このusingステートメントは、多くのクリーンアップ関連の問題をうまく処理しますが、他の多くの問題は残ります。言語とフレームワークの適切なアプローチは、参照を「所有」するストレージの場所IDisposableとそうでないストレージの場所を宣言的に区別することです。参照先を所有するストレージの場所を上書きまたは破棄するIDisposable場合は、反対の指示がない場合にターゲットを破棄する必要があります。
-supercat

1
「コピーコンストラクターは必要ありません」はいいように聞こえますが、実際にはひどく失敗します。java.util.DateとCalendarはおそらく最も有名な例です。何よりも素敵なものはありませんnew Date(oldDate.getTime())
ケビンクライン

2
iow RAIIは「放棄された」わけではなく、単に放棄されることはありませんでした:) (そうでない場合)ディープコピーを作成するのを忘れたため、リソースをコピーすべきではないコピー間で共有していました。
ジュウェンティング

20

Java7はC#に似たものを導入しましたusingtry-with-resourcesステートメント

try1つ以上のリソースを宣言する声明。リソースは、プログラムがそれを終了した後に閉じなければならない対象の通りです。try-with-リソース文は、各リソースは、文の最後に閉じられていることを保証します。を実装するすべてのオブジェクトは、実装するjava.lang.AutoCloseableすべてのオブジェクトを含みjava.io.Closeable、リソースとして使用できます...

だから、彼らは意識的にRAIIを実装しないことを選択しなかったか、その間に気が変わったと思います。


興味深いですが、これはを実装するオブジェクトでのみ機能するようjava.lang.AutoCloseableです。おそらく大したことではありませんが、これがいくぶん制約されていると感じるのは好きではありません。たぶん私は、自動的にrelasedしなければならないいくつかの他のオブジェクトを持っているが、それはそれは実装作ることは非常に意味的に奇妙なAutoCloseable...
FrustratedWithFormsDesigner

9
@パトリック:えー、そう? usingRAIIと同じではありません。ある場合には呼び出し側がリソースの破棄を心配し、別の場合には呼び出し側がそれを処理します。
BlueRaja-ダニーPflughoeft

1
+1リソースの試用については知りませんでした。より多くの定型文をダンプするのに役立つはずです。
jprete

3
using/ try-with-resourcesがRAIIと同じでない場合は-1 。
ショーンマクミラン

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デザイナーはそれらを削除しました。そして、それらがなければ、JavaでRAIIを使用しても意味がありません。


6
「RAIIには意味がない」という意味がわかりません...「JavaでRAIIを提供する能力はない」という意味だと思います... RAIIはどの言語からも独立しています...ありません1つの特定の言語では提供されないため、「無意味」になります
ジョエルファン

4
それは正当な理由ではありません。オブジェクトは、スタックベースのRAIIを使用するために実際にスタック上に存在する必要はありません。「一意の参照」などが存在する場合、デストラクタはスコープ外に出ると起動できます。それはDプログラミング言語でどのように機能するか、インスタンスを参照してください:d-programming-language.org/exception-safe.html
ネマニャTrifunovic

3
@Nemanja:オブジェクトがスタックベースのセマンティクスを持つためにスタック上に存在する必要はありません。しかし、それは問題ではありません。私が言ったように、問題はスタックベースのセマンティクスそのものです。
BlueRaja-ダニーPflughoeft

4
@Aaronaught:悪魔は「ほぼ常に」と「ほとんどの時間」にいます。db接続を閉じずにファイナライザーをトリガーするためにGCに任せない場合、単体テストで正常に機能し、実稼働環境にデプロイすると重大に中断します。確定的なクリーンアップは、言語に関係なく重要です。
ネマンジャトリフノビッチ

8
@NemanjaTrifunovic:ライブデータベース接続でユニットテストを行うのはなぜですか?それは実際には単体テストではありません。いいえ、申し訳ありませんが、購入していません。あなたは、コンストラクタやプロパティを介して渡すべきである、とにかくあらゆる場所にDB接続を作成すべきではない、それはあなたが意味していないスタックのような自動消滅セマンティクスをしたいです。データベース接続に依存するオブジェクトは、実際に所有する必要はほとんどありません。非決定的なクリーンアップが頻繁にあなたを噛んでいるのなら、それは難しいです、それは悪い言語設計ではなく、悪いアプリケーション設計のためです。
アーロンノート

17

の穴の説明usingが不完全です。次の問題を考慮してください。

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

Bar b = new Foo();

// Where's the Dispose?

私の意見では、RAIIとGCの両方を持たないことは悪い考えでした。それはJavaでファイルを閉じるに来るとき、それはだmalloc()free()あそこ。


2
RAIIがミツバチの膝であることに同意します。しかし、このusing句はJavaを介したC#にとって大きな前進です。それは決定論的な破壊を可能にし、したがって正しいリソース管理を可能にします(覚えておく必要があるほどRAIIほど良くはありませんが、間違いなく良いアイデアです)。
マーティンヨーク

8
「Javaでファイルを閉じるときは、malloc()とfree()があります。」–もちろんです。
コンラッドルドルフ

9
@KonradRudolph:mallocやfreeよりも悪いです。少なくともCでは、例外はありません。
ネマンジャトリフノヴィッチ

1
@Nemanja:あなたは、のは公平であることができましょうfree()finally
DeadMG

4
@Loki:基本クラスの問題は、問題としてはるかに重要です。たとえば、オリジナルIEnumerableはから継承せずIDisposable、結果として決して実装できない特別なイテレータがたくさんありました。
DeadMG

14

私はかなり年寄りです。私はそこに行って見たことがあり、何度も頭を打ちました。

私はハースリー・パークで開催された会議で、IBMの少年たちがこの新しいJava言語の素晴らしさを教えてくれました。誰かに尋ねられただけです。彼は私たちがC ++のデストラクタとして知っていることを意味していませんでしたが、ファイナライザもありませんでした(またはファイナライザはありましたが、基本的に機能しませんでした)。これは昔の話で、その時点でJavaはちょっとしたおもちゃの言語であると判断しました。

現在、言語仕様にファイナライザーを追加し、Javaがある程度採用されました。

もちろん、後でGCの速度が大幅に低下したため、ファイナライザーをオブジェクトに配置しないようにみんなに言われました。(ヒープをロックするだけでなく、ファイナライズされるオブジェクトを一時領域に移動する必要があったため、GCがアプリの実行を一時停止したため、これらのメソッドを呼び出すことができませんでした。代わりに、次の直前に呼び出されますGCサイクル)(さらに悪いことに、アプリのシャットダウン時にファイナライザーがまったく呼び出されないことがあります。ファイルハンドルを閉じないことを想像してください)

その後、C#がありました。MSDNのディスカッションフォーラムでは、この新しいC#言語の素晴らしさを教えられました。決定論的なファイナライズが行われない理由を誰かが尋ね、MSの少年たちはそのようなものが必要ないことを教えてくれ、アプリの設計方法を変える必要があると言ってから、GCのすばらしさと古いアプリのすべてを教えてくれましたすべての循環参照のために、ゴミであり、決して機能しませんでした。それから彼らは圧力に屈し、私たちが使用できるスペックにこのIDisposeパターンを追加したと言った。その時点で、C#アプリでの手動メモリ管理にかなり戻ったと思いました。

もちろん、MSの少年たちは後で、彼らが私たちに言ったことはすべて...であることに気づきました。W00t!結局、決定論的なファイナライズは言語に欠けているものであることに気付きました。もちろん、あなたはそれをどこにでも忘れずに置く必要があるので、それでも少し手作業ですが、より良いです。

では、最初から各スコープブロックに使用スタイルのセマンティクスを自動的に配置することができたのに、なぜそうしたのでしょうか。おそらく効率ですが、私は彼らがただ実現しなかったと思うのが好きです。最終的に、.NET(google SafeHandle)のスマートポインターが必要であることに気付いたように、GCは本当にすべての問題を解決すると考えていました。オブジェクトは単なるメモリ以上のものであり、GCは主にメモリ管理を処理するように設計されていることを忘れていました。彼らはGCがこれを処理するという考えに巻き込まれ、あなたがそこに他のものを置くことを忘れていました、オブジェクトはあなたがしばらくそれを削除しなくても問題ではないメモリのブロブではありません。

しかし、元のJavaにfinalizeメソッドがないことは、もう少し多くのものがあると思います-作成したオブジェクトはすべてメモリに関するものであり、他の何か(DBハンドルやソケットなど)を削除したい場合)それからあなたは手動でそれすると予想されました。

Javaは、多くの手動割り当てでCコードを記述することに慣れている組み込み環境向けに設計されたため、自動解放がないことはそれほど問題ではなかったことを思い出してください。この問題は、スレッドやスタック/ヒープとは関係ありませんでした。おそらくメモリ割り当て(および割り当て解除)を少し簡単にするためだけの問題でした。全体的に、try / finallyステートメントは、おそらくメモリ以外のリソースを処理するのに適した場所です。

だから私見、.NETがJavaの最大の欠陥を単純にコピーした方法は、最大の弱点です。.NETは、優れたJavaではなく、優れたC ++である必要がありました。


私見では、「使用」ブロックのようなものは、確定的なクリーンアップのための正しいアプローチですが、さらにいくつかのことが必要です。(2)ディレクティブでDisposeマークされたすべてのフィールドを呼び出すルーチンメソッドを自動生成し、自動的に呼び出すusingかどうかを指定する手段IDisposable.Dispose。(3)に似ているが、例外が発生した場合にusingのみ呼び出すディレクティブDispose。(4)パラメータIDisposableをとるバリエーションException、および
...-supercat

... using必要に応じて自動的に使用されます。パラメータはnullusingブロックが正常に終了した場合、または例外を介して終了した場合に保留中の例外を示します。そのようなことがあれば、リソースを効果的に管理し、リークを回避するのがはるかに簡単になります。
-supercat

11

「Thinking in Java」および「Thinking in C ++」の著者であり、C ++ Standards CommitteeのメンバーであるBruce Eckelは、多くの分野(RAIIだけでなく)で、GoslingとJavaチームが宿題。

...言語が不快で複雑であり、同時に適切に設計される方法を理解するには、C ++のすべてがハングする主要な設計決定を覚えておく必要があります。 、Cプログラマの大衆をオブジェクトに移動させる方法は、移動を透明にすることであると思われます。C++でCコードを変更せずにコンパイルできるようにするためです。これは大きな制約であり、常にC ++の最大の強みであり、その悩みの種でした。これが、C ++をこれまでと同じように成功させ、複雑なものにしました。

また、C ++を十分に理解していないJavaデザイナーをだましました。たとえば、オペレーターのオーバーロードはプログラマーが適切に使用するには難しすぎると考えていました。C ++にはスタック割り当てとヒープ割り当ての両方があり、すべての状況を処理してメモリリークを引き起こさないように演算子をオーバーロードする必要があるため、これは基本的にC ++に当てはまります。確かに難しい。ただし、Javaには単一のストレージ割り当てメカニズムとガベージコレクターがあり、C#で示されたように(ただしJavaに先行するPythonで既に示されていたように)演算子のオーバーロードを簡単にします。しかし、長年の間、Javaチームからの一部の意見は「演算子のオーバーロードは複雑すぎる」というものでした。これと、誰かが明らかにしなかった他の多くの決定」

他にもたくさんの例があります。プリミティブは「効率のために含まれなければなりませんでした」。正しい答えは、「すべてがオブジェクト」に忠実であり、効率が必要な場合に低レベルのアクティビティを実行するためのトラップドアを提供することです(これにより、ホットスポットテクノロジーが最終的に物事をより効率的にすることも可能になります)持ってる)。ああ、超越関数を計算するために直接浮動小数点プロセッサを使用できないという事実(代わりにソフトウェアで行われます)。私はこのような問題についてはできる限り書きましたが、私が聞く答えは常に「これはJavaのやり方だ」という効果に対するトートロジー的な回答でした。

ジェネリックがどれほどひどく設計されているかについて書いたとき、「Javaで行われた以前の(悪い)決定と下位互換性がなければならない」と同じ反応を得ました。最近、ますます多くの人々がジェネリックの十分な経験を積んで、本当に使いにくいことがわかりました。実際、C ++テンプレートははるかに強力で一貫性があります(コンパイラエラーメッセージが許容されるようになったため、使いやすくなりました)。人々は具体化を真剣に考えさえしてきました-役に立つが、自主的な制約によって損なわれるデザインにそれほど大きな凹みを入れない何か。

リストは、退屈なところまで続きます...


5
これは、RAIIに焦点を当てるのではなく、Java対C ++の答えのように聞こえます。C ++とJavaはそれぞれ異なる言語であり、それぞれに長所と短所があると思います。また、C ++デザイナーは多くの分野で宿題をしませんでした(KISS原則が適用されない、クラスが欠落している場合の単純なインポートメカニズムなど)。しかし、質問の焦点はRAIIでした。これはJavaにはないため、手動でプログラムする必要があります。
ジョルジオ

4
@Giorgio:記事の要点は、Javaが多くの問題でボートを逃したように見えることです。そのいくつかはRAIIに直接関連しています。C ++とJavaへの影響に関して、エッケルスは次のように述べています。また、C ++を十分に理解していないJavaデザイナーをだましました。」C ++の設計はJavaに直接影響を与えましたが、C#は両方から学ぶ機会がありました。(そうしたかどうかは別の質問です。)
グナウム

2
@Giorgio特定のパラダイムと言語ファミリーで既存の言語を勉強することは、新しい言語の開発に必要な宿題の一部です。これは、Javaで単純に変更した1つの例です。彼らにはC ++とSmalltalkがありました。C ++には、開発時のJavaがありませんでした。
ジェレミー

1
@Gnawme:「Javaは、RAIIに直接関係するいくつかの問題でボートに乗り遅れたようです」:これらの問題に言及できますか?あなたが投稿した記事はRAIIについて言及していません。
ジョルジオ

2
@Giorgio確かに、C ++の開発以来、そこに欠けている機能の多くを説明する革新がありました。C ++の開発の前に確立された言語を見て、彼らが見つけたはずの機能ありますか?これがJavaで話している宿題のようなものです。Java開発のすべてのC ++機能を考慮しない理由はありません。意図的に除外した多重継承が好きな人もいれば、見落としているように見えるRAIIのような人もいます。
ジェレミー

10

最良の理由は、ここでのほとんどの答えよりもはるかに単純です。

スタックに割り当てられたオブジェクトを他のスレッドに渡すことはできません。

停止して、それについて考えてください。考え続けてください...今、誰もがRAIIに熱心になったとき、C ++にはスレッドがありませんでした。あまりにも多くのオブジェクトを渡すと、Erlang(スレッドごとに別々のヒープ)でさえうるさくなります。C ++はC ++ 2011でのみメモリモデルを取得しました。これで、コンパイラの「ドキュメント」を参照することなく、C ++の同時実行性についてほとんど推論できます。

Javaは(ほぼ)初日から複数のスレッド用に設計されました。

Stroustrupがスレッドを必要としないことを保証する「C ++プログラミング言語」の古いコピーをまだ持っています。

2番目の痛みを伴う理由は、スライスを避けることです。


1
複数のスレッド用に設計されているJavaは、GCが参照カウントに基づいていない理由も説明しています。
dan04

4
@NemanjaTrifunovic:C ++ / CLIをJavaやC#と比較することはできません。ほとんどアンマネージC / C ++コードと相互運用するという明確な目的のために設計されています。逆に、.NETフレームワークへのアクセスを提供するアンマネージ言語に似ています。
アーロンノート

3
@NemanjaTrifunovic:はい、C ++ / CLIは、通常のアプリケーションにまったく不適切な方法でそれを行う方法の一例です。それはだだけで C / C ++相互運用のために有用。通常の開発者、まったく無関係な「スタックまたはヒープ」の決定に悩まされる必要がないだけでなく、それをリファクタリングしようとすると、誤ってnullポインター/参照エラーやメモリリークを作成するのは簡単です。私はないと思うので、申し訳ありませんが、私は、あなたが実際にJavaやC#でプログラミングしている場合疑問に持って誰も持って実際になりたい C ++ / CLIで使用される意味を。
アーロンノート

2
@Aaronaught:私はJava(少し)とC#(たくさん)の両方でプログラミングしましたが、現在のプロジェクトはほとんどすべてC#です。私を信じて、私は私が話していることを知っています、そしてそれは「スタック対ヒープ」とは何の関係もありません-それはあなたがそれらを必要としないとすぐにすべてのリソースが解放されることを確認することに関係しています 自動的に。そうでない場合-あなたはしますトラブルに巻き込まれます。
ネマンジャトリフノビッチ

4
@NemanjaTrifunovic:それは素晴らしい、本当に素晴らしいことですが、C#とC ++ / CLIの両方で、これを実行したいときに明示的に記述する必要があります。それらは異なる構文を使用するだけです。あなたが現在取り乱している本質的なポイントに異議を唱える人はいません(「リソースは不要になるとすぐに解放されます」) -sort-of call-stack-based deterministic処分」。それは水を保持しません。
アーロンノート

5

C ++では、より汎用的な低レベルの言語機能(スタックベースのオブジェクトで自動的に呼び出されるデストラクタ)を使用して、高レベルの機能(RAII)を実装します。あまりにも好き。むしろ、特定のニーズに合わせて特定の高レベルのツールを設計し、既製の言語に組み込まれたプログラマーに提供します。このような特定のツールの問題は、カスタマイズが不可能な場合が多いことです(そのため、学習しやすくなっています)。小さいブロックから構築する場合、時間の経過とともにより良いソリューションが生まれる可能性がありますが、高レベルの組み込みのコンストラクトしかない場合は、これはあまりありません。

ええ、私は(実際にはそこにはいませんでした...)それは、言語を使いやすくすることを目的とした、慎重な決定であったと思いますが、私の意見では、それは悪い決定でした。繰り返しになりますが、私は通常、C ++がプログラマーにチャンスを与え、自分自身をロールするという哲学を好むため、少し偏見があります。


7
「プログラマーに自分のチャンスを与える」という哲学は、それぞれが独自の文字列クラスとスマートポインターをロールしたプログラマーによって書かれたライブラリーを組み合わせる必要があるまで、うまく機能します。
dan04

@ dan04したがって、事前定義された文字列クラスを提供するマネージ言語は、それらをモンキーパッチすることができます。これは、異なる独自のロールされた文字列に対処できない種類の男なら災害のレシピですクラス。
gbjbaanb

-1

Disposeメソッドを使用して、C#でこれに相当する大まかなコードを既に呼び出しています。Javaにもありfinalizeます。 注: Javaのファイナライズは非決定的であり、とは異なることを認識しています。Dispose両方ともGCとともにリソースをクリーニングする方法があることを指摘しています。

ただし、オブジェクトを物理的に破棄する必要があるため、C ++の方が苦痛になります。C#やJavaのような高レベル言語では、ガベージコレクターを使用して、参照がなくなったときにクリーンアップします。C ++のDBConnectionオブジェクトに不正な参照またはそのポインターがないという保証はありません。

はい

私のような他の人がより明確なよりサンドボックス化された言語を好むC ++の低レベルのパワー、制御、および純度のようないくつかは、おそらく好みに帰着します。


12
まずJavaの「最終決定は、」それは...非決定論的であるのではないあなたは、.NETを使用する場合は、C#の"処分"またはC ++ののデストラクタの同等...また、C ++も、ガベージコレクタを持っている
JoelFan

2
@DeadMG:問題は、あなたはばかではないかもしれないが、会社を辞めたばかりの(そしてあなたが現在維持しているコードを書いた)他の人がいたかもしれないということです。
ケビン

7
その男はあなたが何をするにしてもくだらないコードを書くつもりです。悪いプログラマーを連れて良いコードを書かせることはできません。馬鹿を扱うとき、ぶら下がっているポインターは私の懸念の少なくともです。優れたコーディング標準では、追跡する必要のあるメモリにスマートポインターを使用しているため、スマート管理では、メモリの割り当てを安全に解除してアクセスする方法を明確にする必要があります。
DeadMG

3
DeadMGが言ったこと。C ++には多くの悪い点があります。しかし、RAIIは長続きしません。実際、リソース管理を適切に説明するJavaと.NETの欠如(メモリが唯一のリソースであるためですよね?)は、最大の問題の1つです。
コンラッドルドルフ

8
私の意見では、ファイナライザーは災害設計に関して賢明です。オブジェクトの正しい使用をデザイナーからオブジェクトのユーザーに強制しているため(メモリ管理ではなくリソース管理の観点から)。C ++では、リソース管理を正しく行うことはクラス設計者の責任です(一度だけ実行されます)。Javaでは、リソース管理を正しく行うことはクラスユーザーの責任であるため、クラスを使用するたびに実行する必要があります。stackoverflow.com/questions/161177/...
マーティンニューヨーク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.