web-dev-qa-db-ja.com

C ++のガベージコレクション-なぜですか?

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

ガベージコレクションに関する私の唯一の経験は、80年代の安価な家庭用コンピューターのカップルでした。つまり、システムが数秒間フリーズすることがよくありました。それ以来、改善されていると思いますが、ご想像のとおり、それでも高い評価は得られませんでした。

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

45
Head Geek

C++にはガベージコレクションがないという不満をよく耳にします。

ごめんなさい。真剣に。

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

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

別のツール。

Matt Jは彼の投稿でそれを非常に正しく書いています( C++のガベージコレクション-なぜ? ):C++機能のほとんどはCでコーディングできるので、C++機能は必要ありません。それらのほとんどがアセンブリなどでコーディングできるC機能。C++は進化する必要があります。

開発者として:私はGCを気にしません。 RAIIとGCの両方を試しましたが、RAIIの方がはるかに優れていることがわかりました。 Greg Rogersが彼の投稿で述べたように( C++のガベージコレクション-なぜ? )、メモリリークはそれほどひどいものではありません(少なくともC++では、C++が実際に使用されている場合はまれです)。 RAIIの代わりにGCを正当化します。 GCには非決定論的な割り当て解除/ファイナライズがあり、特定のメモリの選択を気にしないコードを書くの方法にすぎません。

この最後の文は重要です。「ジャスト・ドント・ケア」というコードを書くことが重要です。 C++ RAIIでも同様に、RAIIがリソースの解放を行うため、またはコンストラクターが行うためオブジェクトの初期化を行う必要はありません。誰がどのメモリの所有者であるかを気にせずにコーディングすることが重要な場合があります。そして、このコードまたはこのコードに必要なポインターの種類(共有、弱いなど)。 C++ではGCが必要なようです。(私が個人的に見落としたとしても)

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

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

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

GCでは、ツリー構造のデータを使用するだけです。

欠点は、気にしないでくださいwhen「フローティングデータ」は実際に破壊されます。それだけなります破壊されます。

結論

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

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

67
paercebal

簡単に言うと、ガベージコレクションは、原則としてスマートポインタを使用したRAIIと非常に似ています。割り当てるメモリのすべての部分がオブジェクト内にあり、そのオブジェクトがスマートポインタによってのみ参照される場合、ガベージコレクションに近いものがあります(潜在的に優れています)。利点は、すべてのオブジェクトのスコープとスマートポインターについてそれほど賢明である必要がなく、ランタイムに作業を任せることです。

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

11
Matt J

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

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

9
Greg Rogers

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

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

8
MSalters

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

ただし、1つは、標準に準拠したガベージコレクタがデストラクタを呼び出すことはできず、失われたメモリをサイレントに再利用することだけです。

7
coppro

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

まず、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が役に立ちます。

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

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

もちろん、RAIIがgcよりも優れている場合もあります。特に、gcの概念は主にメモリに関係しており、それだけがリソースではありません。ファイルなどはRAIIでうまく処理できます。 pythonまたはRubyのようなメモリ処理のない言語には、これらの場合にRAIIのようなものがあります。ところで(pythonのステートメントを使用)。RAIIは正確にリソースが解放されるタイミングを制御する必要があります。これは、たとえばファイルやロックの場合によくあります。

7

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

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

7
JohnMcG

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

6
tragomaskhalos

ガベージコレクションでは、オブジェクトを延期誰が所有するかを決定できます。

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

プログラムが参照セマンティクスの使用を開始すると(スマートポインターなどを介して)、言語はユーザーをサポートしなくなり、スマートポインターライブラリのウィットに任せられます。

GCで注意が必要なのは、オブジェクトが不要になった場合にwhenを決定することです。

6
xtofl

より簡単なスレッドセーフとスケーラビリティ

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

5
Suma

ガベージコレクションにより、 [〜#〜] rcu [〜#〜] ロックレス同期を正しく効率的に実装するのがはるかに簡単になります。

5
Ben Voigt

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

  • アイテムを解放できる時期を検討してください(すべてのモジュール/クラスはそれで終了していますか?)
  • リソースを解放する準備ができたときにリソースを解放するのは誰の責任かを検討してください(どのクラス/モジュールがこのアイテムを解放する必要がありますか?)

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

リソースと相互作用する複数のモジュールがあり、誰がクリーンアップする必要があるかが明確でない場合、事態はすぐに複雑になり始めます。最終的な結果として、問題に取り組むためのアプローチ全体には、妥協点となる特定のプログラミングおよびデザインパターンが含まれます。

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


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

3
Luke Quinane

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; }
}

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

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

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

2
supercat

私も、C++委員会が本格的なガベージコレクションを標準に追加しているのではないかと疑っています。

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

2
ADEpt

スマートポインタでRAIIを使用すると、その必要がなくなりますよね?

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

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

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

  3. スコープベースの参照カウントは、オブジェクトがスコープの終わりまでリサイクルされないため、浮遊ゴミを維持しますが、トレースGCは、オブジェクトが到達不能になるとすぐにそれらを再利用できます。ループの前に割り当てられたローカルをループ中に再利用できますか?

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

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

2
Jon Harrop

ガベージコレクションは、リークを最悪の悪夢にする可能性があります

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

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

よくある誤解を修正したいと思います。ガベージコレクション神話は、何らかの形でリークを排除します。私の経験から、他の人が書いたコードをデバッグし、最も高価な論理リークを見つけようとする最悪の悪夢は、リソースを大量に消費するホストアプリケーションを介した埋め込みPythonなどの言語によるガベージコレクションでした。

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

ガベージコレクションと理想的とは言えないチームコラボレーションの組み合わせにより、この場合、最悪のデバッグが最も困難なリークが発生しました。

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

いくつかのリソースRを考えると、開発者が常にお互いのコードを注意深く通信およびレビューしているわけではないチーム環境では(私の経験では少し一般的すぎます)、開発者にとっては非常に簡単になりますAそのリソースへのハンドルを格納します。開発者のBも同様に、おそらくデータ構造にRを間接的に追加するあいまいな方法で行います。 Cもそうです。ガベージコレクションシステムでは、これによりRの所有者が3人作成されました。

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

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

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

ガベージコレクションにより、開発者Cは非常に不思議なリークを作成しました。彼のコードは引き続きRにアクセスする可能性があります。これは、ソフトウェアでは見えないエンティティであり、現時点ではユーザーとは関係ありませんが、まだ有効な状態です。そして、Cのコードがより多くのリークを作成するにつれて、彼は無関係なリソースに対してより多くの隠された処理を作成し、ソフトウェアはメモリをリークするだけでなく、毎回遅くなります。

したがって、ガベージコレクションは必ずしも論理リソースリークを軽減するわけではありません。理想的とは言えないシナリオでは、リークがはるかに簡単に気付かれずにソフトウェアに留まる可能性があります。開発者は、GCの論理リークを追跡しようとするとイライラする可能性があるため、回避策としてソフトウェアを定期的に再起動するようにユーザーに指示するだけです。それはダングリングポインタを排除します、そしてどんなシナリオでもクラッシュが完全に受け入れられない安全に取りつかれたソフトウェアでは、私はGCを好むでしょう。しかし、私は安全性はそれほど重要ではないが、リソースを大量に消費するパフォーマンスが重要な製品で作業することがよくあります。この製品では、非常にあいまいで神秘的なサイレントバグよりも、迅速に修正できるクラッシュの方が望ましいです。リソースリークは些細なバグではありません。

どちらの場合も、3Dソフトウェアのシーングラフや、コンポジターで利用できるビデオクリップ、ゲームの世界の敵など、スタックに存在しない永続オブジェクトについて話します。リソースがその存続期間をスタックに結び付ける場合、C++と他のGC言語の両方が、リソースを適切に管理することを簡単にする傾向があります。本当の難しさは、他のリソースを参照する永続的なリソースにあります。

CまたはC++では、リソースの所有者とそのハンドルを解放するタイミングを明確に指定しなかった場合、セグメンテーション違反が原因でダングリングポインターとクラッシュが発生する可能性があります(例:イベントに応答してnullに設定)。それでもGCでは、その大音量で不快な、しかししばしば見つけやすいクラッシュは、決して検出されないかもしれないサイレントなリソースリークと交換されます。

1
Dragon Energy