48

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

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

経験豊富な C++ 開発者にとって、ガベージ コレクションにはどのような利点がありますか?

4

16 に答える 16

72

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++のさらに別の優れたツールになります

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

于 2008-10-23T06:29:03.370 に答える
12

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

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

于 2008-10-23T05:35:24.180 に答える
9

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

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

于 2008-10-23T05:49:06.587 に答える
8

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 拡張機能)。finally in python が何かの実行を保証しないのと同じ理由です。この場合、GC が役立ちます。

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

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

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

于 2008-10-23T13:00:29.510 に答える
8

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

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

于 2008-10-23T14:35:05.840 に答える
7

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

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

于 2008-10-23T05:30:21.523 に答える
7

経験豊富な C++ 開発者にとって、ガベージ コレクションにはどのような利点がありますか?

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

于 2008-10-23T14:15:49.543 に答える
6

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

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

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

GC で難しいのは、オブジェクトがいつ不要になるかを判断することです。

于 2008-10-23T12:37:42.020 に答える
6

C++ にはガベージ コレクションが言語に組み込まれていないため、C++ 時代にはガベージ コレクションを使用できないと想定するのは、よくある間違いです。これはナンセンスです。仕事で当然のこととして Boehm コレクターを使用するエリート C++ プログラマーを知っています。

于 2008-10-25T09:36:57.427 に答える
5

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

于 2012-02-03T03:24:17.867 に答える
3

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

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

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

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

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


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

于 2008-10-23T06:50:35.870 に答える
2

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

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

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

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

于 2015-02-04T19:56:29.840 に答える
2

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

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

于 2008-10-23T05:58:30.297 に答える
1

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

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

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

よくある誤解を正したいと思います。それは、ガベージ コレクションによって何らかの形でリークが解消されるという神話です。私の経験から言うと、他の人が書いたコードをデバッグし、最もコストのかかる論理リークを見つけようとする最悪の悪夢は、リソース集約型のホスト アプリケーションを介した組み込み Python などの言語によるガベージ コレクションに関係していました。

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

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

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

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

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

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

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

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

そのため、ガベージ コレクションによって論理リソースのリークが軽減されるとは限りません。理想的とは言えないシナリオでは、リークが気付かれずにソフトウェアに残りやすくなる可能性があります。開発者は、GC の論理リークを追跡しようとしてイライラする可能性があり、回避策として定期的にソフトウェアを再起動するようにユーザーに指示するだけです。ぶら下がっているポインターを排除します。また、どのようなシナリオでもクラッシュがまったく受け入れられない安全性重視のソフトウェアでは、GC を使用することをお勧めします。しかし、私は安全性がそれほど重要ではないが、リソースを集中的に使用し、パフォーマンスが重要な製品で作業していることがよくあります。そこでは、本当にあいまいで謎めいたサイレント バグよりも、すぐに修正できるクラッシュの方が望ましく、リソース リークは些細なバグではありません。

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

C または C++ では、誰がリソースを所有しているか、およびそれらへのハンドルをいつ解放する必要があるかを明確に指定できない場合 (例: イベントに応答して null に設定)、segfault に起因するダングリング ポインターとクラッシュが発生する可能性があります。しかし GC では、大声で不快だが、しばしば見つけやすいクラッシュが、決して検出されない可能性がある静かなリソース リークと交換されます。

于 2015-05-06T13:25:13.173 に答える