質問

C++ にはガベージ コレクションがないという人々の不満の声をよく聞きます。C++ 標準委員会がそれを言語に追加することを検討しているとも聞きました。申し訳ありませんが、意味がわかりません...スマート ポインターで RAII を使用すると、その必要がなくなります。

私がガベージ コレクションに関する唯一の経験をしたのは、80 年代の家庭用の安価なコンピュータ 2 台でした。その場合、システムが数秒間フリーズすることが頻繁にありました。それ以来改善されているのは確かですが、ご想像のとおり、私はそれについて高い評価を残していませんでした。

ガベージ コレクションは経験豊富な C++ 開発者にどのような利点をもたらしますか?

役に立ちましたか?

解決

C ++にはガベージコレクションがないという苦情が寄せられています。

私は彼らにとても申し訳ありません。真剣に。

C ++にはRAIIがあり、ガベージコレクションされた言語でRAII(または去勢されたRAII)が見つからないと常に文句を言います。

ガベージコレクションは、経験豊富なC ++開発者にどのような利点をもたらしますか?

別のツール。

Matt Jは彼の投稿で非常に正確に書いています( C ++のゴミ収集-なぜ?):C ++の機能のほとんどはCでコーディングできるため、C ++の機能は必要ありません。ほとんどの機能はアセンブリなどでコーディングできるため、Cの機能は必要ありません。 C ++は進化する必要があります。

開発者として:GCは気にしません。 RAIIとGCの両方を試しましたが、RAIIは非常に優れていると思います。 Greg Rogersの投稿( C ++のガベージコレクション-なぜ?)、メモリリークはRAIIではなくGCを正当化するほどひどくはありません(少なくともC ++では、C ++が実際に使用されている場合はまれです)。 GCには非決定的な割り当て解除/最終化機能があり、特定のメモリの選択を気にしないコードを書く方法にすぎません。

この最後の文は重要です。「ジャスト・ドント・ケア」コードを書くことが重要です。 C ++ RAIIの場合と同様に、RAIIは私たちのためにリソースを解放するので、リソースの解放は気にしません。そして、このコードまたはこのコードに必要なポインター(共有、脆弱など)。 C ++でGCが必要なようです。(私が個人的にそれを見損ねたとしても)

C ++でのGCの適切な使用例

アプリには、「フローティングデータ」がある場合があります。データのツリーのような構造を想像してください。しかし、実際に「所有者」ではありません。のデータ(そして、いつ正確にデータが破壊されるかを誰も本当に気にしません)。複数のオブジェクトがそれを使用し、それを破棄できます。誰も使用しなくなったときに解放する必要があります。

C ++のアプローチでは、スマートポインターを使用しています。 boost :: shared_ptrが思い浮かびます。そのため、各データは独自の共有ポインターによって所有されます。クール。問題は、各データが別のデータを参照できる場合です。共有ポインターは参照カウンターを使用しているため使用できません。参照カウンターは循環参照をサポートしません(AはBを、BはAを指します)。そのため、弱いポインター(boost :: weak_ptr)を使用する場所と、共有ポインターを使用するタイミングについてよく考える必要があります。

GCでは、ツリー構造のデータを使用します。

「浮遊データ」をいつ気にしてはいけないというマイナス面本当に破壊されます。それだけが破壊されます

結論

最終的に、適切に行われ、現在のC ++のイディオムと互換性がある場合、GCは C ++のもう1つの優れたツールになります。

C ++はマルチパラダイム言語です。GCを追加すると、おそらく反逆罪のためにC ++のファンが泣きそうになりますが、最終的には良いアイデアかもしれません。機能が言語を壊すので、C ++に干渉しない正しいC ++ GCを有効にするために必要な作業を行うと信頼できます。 C ++では、機能が必要ない場合は、費用はかかりません。

他のヒント

簡単に言うと、ガベージ コレクションは原理的にはスマート ポインターを備えた RAII と非常に似ているということです。割り当てたすべてのメモリがオブジェクト内にあり、そのオブジェクトがスマート ポインターによってのみ参照される場合、ガベージ コレクションに近いもの (より優れたものになる可能性があります) が得られます。この利点は、すべてのオブジェクトのスコープ設定とスマート ポインティングについて慎重になる必要がなく、ランタイムに作業を任せられることによって得られます。

この質問は、「経験豊富なアセンブリ開発者に C++ は何を提供する必要がありますか?」に似ているように思えます。命令とサブルーチンがあればその必要はありませんね?」

valgrindのような優れたメモリチェッカーの登場により、ガーベッジコレクションをセーフティケースとして使用することはあまりないようです。何かの割り当てを解除するのを忘れました-特にメモリ以外のリソースのより一般的なケースを管理するのにあまり役立たないので(これらはあまり一般的ではありませんが)。加えて、明示的にメモリを割り当てたり、割り当てを解除したりすることは(スマートポインターを使用しても)私が見たコードではかなりまれです。コンテナーは通常、はるかにシンプルで優れた方法だからです。

ただし、ガベージコレクションは、特に多数の短命オブジェクトがヒープに割り当てられている場合、潜在的にパフォーマンス上の利点を提供できます。 GCは、新しく作成されたオブジェクトの参照の局所性を向上させる可能性もあります(スタック上のオブジェクトと同等)。

C ++でのGCサポートの動機付けの要因は、ラムダプログラミング、匿名関数などのようです。ラムダライブラリは、クリーンアップを気にせずにメモリを割り当てることができるという利点があります。通常の開発者にとっての利点は、ラムダライブラリのコンパイルがより簡単で、信頼性が高く、高速になることです。

GCは、無限のメモリのシミュレーションにも役立ちます。 PODを削除する必要があるのは、メモリをリサイクルする必要があるからです。 GCまたは無限のメモリがある場合、PODを削除する必要はありません。

委員会はガベージコレクションを追加するのではなく、ガベージコレクションをより安全に実装できる機能をいくつか追加しています。将来のコンパイラに実際に何らかの影響があるかどうかは、時が経てばわかります。特定の実装は大きく異なる可能性がありますが、ほとんどの場合、到達可能性ベースのコレクションが含まれ、その方法によっては、わずかにハングする可能性があります。

1つのことは、標準に準拠したガベージコレクターがデストラクタを呼び出せないことです。失われたメモリを静かに再利用するためだけです。

ガベージコレクションは、経験豊富なC ++開発者にどのような利点をもたらしますか?

経験の浅い同僚のコードでリソースリークを追跡する必要はありません。

RAIIがGCに取って代わる、または非常に優れていると主張する方法を理解できません。 gcによって処理される多くのケースがあり、RAIIはまったく対処できません。彼らは別の獣です。

第一に、RAIIは完全な証拠ではありません。C++に一般的ないくつかの一般的な障害に対して機能しますが、RAIIがまったく役に立たない場合が多くあります。非同期イベント(UNIXでのシグナルなど)に対して脆弱です。基本的に、RAIIはスコープに依存しています。変数がスコープ外の場合、変数は自動的に解放されます(デストラクタがもちろん正しく実装されていることを前提としています)。

auto_ptrとRAIIのどちらも役に立たない簡単な例を次に示します。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <memory>

using namespace std;

volatile sig_atomic_t got_sigint = 0;

class A {
        public:
                A() { printf("ctor\n"); };
                ~A() { printf("dtor\n"); };
};

void catch_sigint (int sig)
{
        got_sigint = 1;
}

/* Emulate expensive computation */
void do_something()
{
        sleep(3);
}

void handle_sigint()
{
        printf("Caught SIGINT\n");
        exit(EXIT_FAILURE);
}

int main (void)
{
        A a;
        auto_ptr<A> aa(new A);

        signal(SIGINT, catch_sigint);

        while (1) {
                if (got_sigint == 0) {
                        do_something();
                } else {
                        handle_sigint();
                        return -1;
                }
        }
}

Aのデストラクタは呼び出されません。もちろん、これは人為的でやや不自然な例ですが、実際には同様の状況が発生する可能性があります。たとえば、SIGINTを処理し、まったく制御できない別のコードによってコードが呼び出された場合(具体例:matlabのmex拡張)。最終的にpythonが何かの実行を保証しないのと同じ理由です。この場合、GCがお手伝いします。

他のイディオムはこれではうまくいきません:些細でないプログラムでは、ステートフルオブジェクトが必要になります(ここでは非常に広い意味でオブジェクトという言葉を使用していますが、言語で許可されている任意の構成を使用できます)。 1つの関数の外側で状態を制御する必要がある場合、RAIIを使用して簡単に制御することはできません(これが、RAIIが非同期プログラミングにそれほど役に立たない理由です)。 OTOH、gcは、プロセスのメモリ全体のビューを持ちます。つまり、プロセスが割り当てたすべてのオブジェクトを認識しており、非同期にクリーニングできます。

同じ理由で、gcを使用する方がはるかに高速です。多くのオブジェクト(特に小さなオブジェクト)の割り当て/割り当て解除が必要な場合、gcはカスタムアロケータを記述しない限り、RAIIを大幅に上回ります。 1回のパスで多くのオブジェクトを割り当て/クリーニングできます。パフォーマンスが重要な場合でも、いくつかの有名なC ++プロジェクトはgcを使用します(Unreal Tournamentでのgcの使用については、たとえばTim Sweenieを参照してください: http://lambda-the-ultimate.org/node/1277 )。基本的に、GCはレイテンシーを犠牲にしてスループットを向上させます。

もちろん、RAIIがgcよりも優れている場合があります。特に、gcの概念は主にメモリに関するものであり、それが唯一のリソースではありません。ファイルなどのようなものはRAIIでうまく処理できます。 pythonやrubyのようなメモリ処理のない言語には、BTW(pythonのステートメントを使用)の場合、RAIIのようなものがあります。 RAIIは、リソースをいつ解放するかを正確に制御する必要がある場合に非常に役立ちます。これは、たとえばファイルやロックの場合によくあります。

C ++にはガベージコレクションが言語に焼き付けられていないため、C ++期間ではガベージコレクションを使用できないと想定するのは一般的なエラーです。これはナンセンスです。私は仕事でBoehmコレクターを使用しているエリートC ++プログラマーを知っています。

ガベージコレクションを使用すると、オブジェクトの所有者に関する決定を延期できます。

C ++は値のセマンティクスを使用するため、RAIIでは、実際、スコープ外に出たときにオブジェクトが再収集されます。これは「即時GC」と呼ばれることもあります。

プログラムが(セマンティックポインターなどを介して)参照セマンティクスの使用を開始すると、言語でサポートされなくなり、スマートポインターライブラリの機能が残ります。

GCについての厄介なことは、オブジェクトが不要になった を決定することです。

ガベージコレクションを使用すると、 RCU ロックレス同期がはるかに簡単に正しく効率的に実装できます。

より簡単なスレッドの安全性とスケーラビリティ

GCには、いくつかのシナリオで非常に重要なプロパティが1つあります。ポインタの割り当てはほとんどのプラットフォームで当然アトミックですが、スレッドセーフな参照カウント(「スマート」)ポインタの作成は非常に難しく、同期のオーバーヘッドが大きくなります。その結果、スマートポインターはしばしば「うまくスケールしない」と言われます。マルチコアアーキテクチャ。

ガベージコレクションは、実際には自動リソース管理の基礎です。また、GCを使用すると、定量化が困難な方法で問題に取り組む方法が変わります。たとえば、手動のリソース管理を行う場合、次のことを行う必要があります。

  • アイテムをいつ解放できるかを検討します(すべてのモジュール/クラスが終了しますか?)
  • リソースを解放する準備ができたら、リソースを解放する責任を負います(どのクラス/モジュールがこのアイテムを解放する必要がありますか?)

些細なケースでは、複雑さはありません。例えば。メソッドの開始時にファイルを開き、終了時にファイルを閉じます。または、呼び出し元はこの返されたメモリブロックを解放する必要があります。

リソースと対話する複数のモジュールがあり、誰がクリーンアップする必要があるかが明確でない場合、事はすぐに複雑になり始めます。最終結果は、問題に取り組むためのアプローチ全体が、妥協である特定のプログラミングおよび設計パターンを含むことです。

ガベージコレクションがある言語では、使い捨て終了したことがわかっているリソースを解放できるパターン。ただし、解放に失敗した場合は、GCがその日を節約します。


実際に私が言及した妥協の完璧な例であるスマートポインタ。バックアップメカニズムがない限り、スマートポインタを使用しても、循環データ構造のリークを防ぐことはできません。この問題を回避するために、巡回構造が最適である場合でも妥協し、循環構造の使用を避けることがよくあります。

私も、C ++のコミッティーが本格的なガベージコレクションを標準に追加していることに疑問を抱いています。

しかし、現代の言語でガベージコレクションを追加/使用する主な理由は、ガベージコレクションに対して反対の正当な理由が少なすぎることです。 80年代以来、メモリ管理とガベージコレクションの分野でいくつかの大きな進歩があり、ソフトリアルタイムのような保証を提供できるガベージコレクション戦略さえあると思います(&quot; GCはを超えることはありません。 ...最悪の場合&quot;)。

  

スマートポインターでRAIIを使用すると、不要になりますか?

スマートポインターは、ガベージコレクション(自動メモリ管理)の形式であるC ++で参照カウントを実装するために使用できますが、いくつかの重要な欠陥があるため、本番GCは参照カウントを使用しなくなりました。

  1. 参照カウントリークサイクル。 A&#8596; Bを考えてみましょう。オブジェクトAとBは両方とも相互に参照しているため、両方とも1の参照カウントを持ち、どちらも収集されませんが、両方とも回収する必要があります。 試用版削除などの高度なアルゴリズムは、この問題を解決しますが、多くを追加します複雑さの。回避策として weak_ptr を使用すると、手動メモリ管理にフォールバックします。

  2. 単純な参照カウントは、いくつかの理由で遅いです。まず、キャッシュ外の参照カウントを頻繁にバンプする必要があります( Boostのshared_ptrは最大10&#215; OCamlのガベージコレクションよりも遅い)。第二に、スコープの最後に挿入されたデストラクタは、不必要で高価な仮想関数呼び出しを招き、末尾呼び出しの削除などの最適化を妨げる可能性があります。

  3. スコープベースの参照カウントは、スコープの最後までオブジェクトがリサイクルされないため、浮遊ガベージを保持しますが、トレースGCは到達できなくなるとすぐにそれらを回収できます。ループの前に割り当てられたローカルをループ中に再生できますか?

  

ガベージコレクションは、経験豊富なC ++開発者にどのような利点をもたらしますか?

生産性と信頼性が主な利点です。多くのアプリケーションでは、手動のメモリ管理にはプログラマーの多大な労力が必要です。ガベージコレクションは、無限メモリマシンをシミュレートすることにより、プログラマーをこの負担から解放し、問題解決に集中し、バグのいくつかの重要なクラス(ぶら下がりポインター、 free 、double free )。さらに、ガベージコレクションは、他の形式のプログラミングを容易にします。 上向きfunarg問題(1970)

GCをサポートするフレームワークでは、文字列などの不変オブジェクトへの参照をプリミティブと同じ方法で渡すことができます。クラス(C#またはJava)を検討してください:

public class MaximumItemFinder
{
  String maxItemName = "";
  int maxItemValue = -2147483647 - 1;

  public void AddAnother(int itemValue, String itemName)
  {
    if (itemValue >= maxItemValue)
    {
      maxItemValue = itemValue;
      maxItemName = itemName;
    }
  }
  public String getMaxItemName() { return maxItemName; }
  public int getMaxItemValue() { return maxItemValue; }
}

このコードは、文字列のコンテンツを処理する必要はなく、単純にプリミティブとして扱うことができることに注意してください。 maxItemName = itemName; のようなステートメントは、おそらくレジスタロードとレジスタストアの2つの命令を生成します。 MaximumItemFinder には、 AddAnother の呼び出し元が、渡された文字列への参照を保持するかどうかを知る方法がありません。また、呼び出し元には、< code> MaximumItemFinder はそれらへの参照を保持します。 getMaxItemName の呼び出し元は、 MaximumItemFinder および返された文字列の元のサプライヤが、それへのすべての参照を放棄したかどうか、およびそのタイミングを知る方法がありません。ただし、コードは単純にプリミティブ値のように文字列参照を渡すことができるため、これらはどれも重要ではありません

上記のクラスは、 AddAnother の同時呼び出しが存在する場合、スレッドセーフではありませんが、 GetMaxItemName の呼び出しは、空の文字列または AddAnother に渡された文字列のいずれかへの有効な参照を返します。最大アイテム名とその値の間の関係を確保したい場合、スレッドの同期が必要になりますが、メモリがない場合でも安全です

C ++で上記のようなメソッドを記述する方法はないと思います。この方法は、スレッド同期を使用せずに、またはすべての文字列変数に独自のコピーを必要とせずに、任意のマルチスレッド使用時にメモリの安全性を維持します該当する変数の存続期間中に解放または再配置されない可能性のある、独自のストレージスペースに保持されるコンテンツの int ほど安価に定義、割り当て、および渡すことができる文字列参照型を定義することは確かに不可能です。

ガベージコレクションは、リークを最悪の悪夢にすることができます

循環参照などを処理する本格的なGCは、参照カウントされた shared_ptr をアップグレードしたものになります。 C ++では多少歓迎しますが、言語レベルでは歓迎しません。

C ++の利点の1つは、ガベージコレクションを強制しないことです。

よくある誤解を修正したい:ガベージコレクション myth は、何らかの形でリークを排除します。私の経験から、他の人が書いたコードをデバッグし、最も高価な論理リークを見つけようとする最悪の悪夢は、リソースを集中的に使用するホストアプリケーションを介した組み込みPythonなどの言語によるガベージコレクションに関係していました。

GCのような主題について話すとき、理論があり、次に実践があります。理論的には素晴らしいし、漏れを防ぎます。しかし、理論レベルでは、すべての言語は素晴らしく、漏れのないものです。理論上は、誰もが完全に正しいコードを記述し、単一のコードが失敗する可能性のあるあらゆるケースをテストします。

ガベージコレクションと理想的ではないチームコラボレーションが組み合わされた場合、このケースでは最悪でデバッグが困難なリークが発生しました。

まだ問題はリソースの所有権に関係しています。永続オブジェクトが関係する場合は、ここで明確な設計上の決定を下す必要があります。ガベージコレクションを行うと、そうではないと考えるのが非常に簡単になります。

開発者が常にお互いのコードを注意深くやり取りしていないチーム環境で R を与えられた場合(私の経験では少し一般的すぎる)開発者 A がそのリソースへのハンドルを保存するのは非常に簡単です。開発者の B も同様に、おそらく曖昧な方法で、間接的に R をデータ構造に追加します。 C も同様です。ガベージコレクションされたシステムでは、これにより R の3人の所有者が作成されました。

開発者 A は元々リソースを作成し、自分がその所有者であると考えているため、ユーザーが彼を示したときに R への参照をリリースすることを忘れないもう使いたくない結局、彼がそうしなければ、何も起こらず、ユーザー側の削除ロジックが何もしなかったことはテストから明らかです。だから彼は、合理的に有能な開発者がそうするように、それをリリースすることを覚えています。これは、 B が処理するイベントをトリガーし、 R への参照を解放することも忘れないでください。

ただし、 C は忘れます。彼はチームの強力な開発者の1人ではありません。システムで1年しか働いていない、やや新鮮な新人です。あるいは、彼はチームではなく、多くのユーザーがソフトウェアに追加する製品のプラグインを書いている人気のあるサードパーティの開発者です。ガベージコレクションでは、これらのサイレントな論理リソースリークが発生します。それらは最悪の種類です:プログラムの実行期間中、メモリ使用量が何らかの不思議な目的のために増え続けているという事実に加えて、ソフトウェアのユーザーに見える側に明らかなバグとして必ずしも現れるわけではありません。デバッガーでこれらの問題を絞り込むことは、時間依存の競合状態をデバッグするのと同じくらい楽しい場合があります。

ガベージコレクションがなければ、開発者の C ダングリングポインターを作成します。彼はある時点でそれにアクセスして、ソフトウェアをクラッシュさせようとするかもしれません。これはテスト/ユーザーに見えるバグです。 C は少し恥ずかしくなり、バグを修正します。 GCシナリオでは、システムのどこがリークしているかを把握しようとするのは非常に難しいため、一部のリークは修正されません。これらは、簡単に検出でき、特定のコード行に特定できる valgrind タイプの物理的リークではありません。

ガベージコレクション、開発者

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top