C++におけるリソース管理について

7386 ワード

C++の複雑さは基本的な事実であり、これも多くの人がC++を非難している原因となっている.実際、陳皓が「C++の配列はマルチステートをサポートしていません」?という文章で述べたように、C++を本当に理解していない場合、このような結論を出すのが好きな人が多い.さらに、C言語そのものの「穴」もC++の問題にまとめる.このような人は実に少なくなくて、C++11は最も論争の言語の1つとして、毎回言語の選択に関わることを討論する時、すべて1度の“血戦”を引き起こして、しかし結果は往々にしてやめて、C++の引き続きC++陣営を守ることが好きで、C++の精力を次回の暗いC++の時まで残すことが嫌いです.客観的公正にC++を批判することに対して、私の心の中は尊敬して感心します;まだC++がはっきりしていないのに、私は軽蔑しています.どの言語にも独自の歴史的背景と位置づけがあり、C++はこのように設計されており、歴史的に見ると、Cを互換化するために、Cプログラムを修正せずに使用することができるようになっている.位置決めから見ると3つの制約:Cとの完全な互換性、静的タイプチェック、最高パフォーマンスです.私は心から望んで、もし誰かがC++が好きではないならば、それを理解した後に更に暗くて、人の子弟を誤らないようにします.
C++の良さを書くたびに、ちゃんと発散したいので、よし、本題に戻ります.この文章はC++の中の資源管理を検討したいと思って、資源管理といえば、異常な安全を話さなければならなくて、異常があるからこそ、資源管理をもっと重要にします.C++11はこの問題を処理するために非常に良いプログラミングIdomを提供し、C++11の新しい特性はこれらのIdomをより使いやすくした.
計算中のリソースは非常に広範な概念であり、メモリ、ロック、ファイル、Socketなどはすべてリソースであり、C++ではこれらのリソースを統一的に管理することができる.すなわちRAIである(The C++Programming Lauguage,Special Edition,p 364,14.4節を参照).その基本構想は非常に簡単で、クラスで資源をカプセル化し、クラスの構造関数で資源を取得し、クラスの構造関数で資源を解放する.使用すると、このクラスをスタックの上にインスタンス化し、このオブジェクトが役割ドメインを超えた場合、このオブジェクトの構造関数が呼び出され、リソースが解放されます.この簡単な方式こそ、C++資源管理の基礎を構成し、このような方式は異常に安全である.2、クラスのコンストラクション関数に異常が発生した場合、C++コンパイラはリソースの漏洩が発生しないことを保証する(Exceptional C+,p 26,Item 8を参照).3、オブジェクトの構築後に異常が発生し、stack unwinding(The C++Programming Lauguage,Special Edition,p 355,14.1節を参照)の過程で、C++標準はコンパイラに現在のスタックの上で成功して構築したオブジェクトの構造関数が必ず調整され、メモリが必ず解放されることを保証することを要求した.
インテリジェントポインタはRAIIの実装例であり、メモリ管理に特化しており、C++11には3つのインテリジェントポインタがある:unique_ptr、shared_ptrとweak_ptr.auto_ptrはもう時代遅れで、その機能はunique_ptrは置換され,後者はSTL容器に用いることができる.
他のリソースについては,ユーザ自身がカプセル化する必要があり,同様のリソースは一度カプセル化すれば後で使用するのに便利である.資源ごとにクラスで包装するのが面倒だと嫌なら、ScopeGuardを利用して処理することができ、この施設はAndrei Alexandrescuによって発明され、劉未鵬はC++11(および現代C++スタイル)と高速反復開発で詳しく紹介した.もちろん、ScopeGuardはC++11(std::functionとlambda式のおかげ)で、非常に使いやすいレベルに達しました.こちらにSocpeGuardのコードを貼っておきますが、興味のある方は劉未鵬の文章を参考にしてください.
class ScopeGuard
{
public:
    explicit ScopeGuard(std::function<void()> onExitScope)
        : onExitScope_(onExitScope), dismissed_(false)
    { }

    ~ScopeGuard()
    {
        if(!dismissed_)
        {
            onExitScope_();
        }
    }

    void Dismiss()
    {
        dismissed_ = true;
    }

private:
    std::function<void()> onExitScope_;
    bool dismissed_;

private: // noncopyable
    ScopeGuard(ScopeGuard const&);
    ScopeGuard& operator=(ScopeGuard const&);
};

簡単に使えます.Cでリソースを申請するとき:
HANDLE h = CreateFile(...);
ScopeGuard onExit([&] { CloseHandle(h); });

このように申請資源と解放資源は永遠に一緒に書かれており、忘れず、資源が永遠に漏れないことを保証しています.
リソースの漏洩といえば、当然メモリの漏洩が考えられますが、Visual C++では、メモリの漏洩を検出する方法があります.
cppファイルの先頭に、次のコードを追加します.
#ifdef _DEBUG
#define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
#define DEBUG_CLIENTBLOCK
#endif // _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif // _DEBUG

関数に、次のような関数呼び出しを追加します.
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);

これにより、プログラムがDebugモードで実行された後、メモリ漏れが発生した場合、以下のような検出情報が印刷される.
Detected memory leaks!
Dumping objects ->
{197} normal block at 0x00FBCE98, 44 bytes long.
 Data: <                > 0A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete.

C++リソース管理のメリットについて話し、Javaに突っ込みます.Javaはスローガンを叫ぶのが好きな言語で、「一度コンパイルして、あちこちで実行(Write once,run anywhere)」、「プログラマーはメモリの漏れを心配する必要はありません」「純粋なオブジェクト向け言語」などです.他はともかく、リソース管理について話します.Javaにおけるリソースの管理は2つに分けられ、1つはメモリであり、gcによって管理される.2つ目は、try、catch、finallyによって管理される他のリソースです.1つ目の問題は、統一されていないこと、Javaの中で統一されていないところ、例えば基礎データ型がテンプレートパラメータとして機能しないこと、「純粋なオブジェクト向け言語」なのに基本データ型があることなどです.2つ目の問題は、try、catch、finallyの文法が醜いことです.そして、最も不快なのはfinallyの中でtryにいることです.卵が痛いです.3つ目の問題は、メモリがJVMに管理されているため、gcのタイミングが不確定になり、Javaの普及範囲に深刻な影響を及ぼしていることです(大規模なゲームでは、bossをしていますが、突然gcになって、それから、それからはありません).Javaは使いやすい言語であり、プログラマーの開発効率を大幅に向上させることができることを認めます.特にJavaEEシステムは、確かに成熟しています.私自身もJavaコードをたくさん書いたことがありますが、一番便利なのは間違いがあった後、異常スタックがバグに位置するのを迅速に助けることができます.しかしJavaは、あまりにも多くの下位情報を遮断しているため、多くの不合格なプログラマーを育成しています.ここではJavaプログラマーが優秀ではないというわけではありませんが、Javaをずっと使ってC/C++を理解しなければ、本当に退化しやすいです.