1

私が次のようなものを持っているとしましょう:

class obj001
{
public:
    obj001() {
        std::cout << "ctor == obj001" << std::endl;
    }

    ~obj001() {
        std::cout << "dtor == obj001" << std::endl;
    }
};

class obj002
{
public:
    obj002() {
        std::cout << "ctor == obj002" << std::endl;
    }

    ~obj002() {
        std::cout << "dtor == obj002" << std::endl;
    }
};

class packet001
{
public:
    packet001(): p01(NULL), p02(NULL) {
        /*p01 = new obj001;
        p02 = new obj002;
        throw "hahaha";*/

        std::cout << "CTOR == PACKET01" << std::endl;
    }

    ~packet001() {
        delete p01;
        delete p02;

        std::cout << "DTOR == PACKET01" << std::endl;
    }

    void init() {
        p01 = new obj001;
        p02 = new obj002;
        throw "hahaha";
    }

    obj001* p01;
    obj002* p02;
};

そして私がそうするなら:

try
{       
    packet001 superpack;
    superpack.init();
}
catch(char* type)
{

}

その後、init()失敗し、のDtorsuperpackが呼び出されます。

しかし、メモリ割り当てをのCtor内に配置するsuperpackと(もちろん
実行しないでください) 、Ctorが失敗した後、Dtorは呼び出されないため、リークされます。init()
p01p02

だから、init()

ありがとう!

4

4 に答える 4

4

2フェーズ構築を使用すると、通常の構築とそれに続く関数への外部呼び出しは、init構築後、有効なオブジェクトが手元にあるかどうかがまだわからないことを意味します。つまり、引数などのオブジェクトを取得する関数では、オブジェクトが有効かどうかがわかりません。これは、多くの追加のチェックと不確実性を意味し、バグと追加の作業を意味します。したがって、コンストラクターは、代わりに完全に機能する有効なオブジェクトを確立する必要があります。

「機能的で有効な」という概念に入る一連の仮定は、クラス不変量と呼ばれます。

つまり、より学術的な言い回しでは、コンストラクターの仕事はクラス不変条件を確立することであり、その結果、構築後に保持されることが知られています。

次に、外部で使用可能なすべての操作でオブジェクトを有効に保つことは、オブジェクトが引き続き有効であることが保証されることを意味します。したがって、それ以上の妥当性チェックは必要ありません。このスキームは、すべてのオブジェクトに完全に適用できるわけではありません(反例は、ファイルを表すオブジェクトであり、操作によってオブジェクトが事実上無効になる可能性があります)が、ほとんどの場合、それは良い考えであり、うまく機能します。直接機能するのではなく、パーツに対して機能します。

したがって、コンストラクターでは、次のいずれかの方法でクリーンアップを確認する必要があります。

  • 生の配列と動的割り当てを直接処理する代わりに、標準ライブラリコンテナ(またはサードパーティのもの)を使用します。

  • または、それぞれが1つのリソースのみを管理するサブオブジェクトを使用します。サブオブジェクトは、データメンバーまたは基本クラスにすることができます。データメンバーの場合、それはスマートポインタである可能性があります。

  • または、最悪の場合、直接クリーンアップにtry-を使用します。catch

必要に応じて、戻り値をチェックするというCの考え方を使用して、直接クリーンアップを呼び出すことも技術的に可能です。ただし、上記のリストは、使いやすさと安全性の高い順になっています。Cスタイルのコーディングは、そのリストの一番下を超えています。


C++言語の作成者であるBjarneStroustrupは、この主題について、彼の付録付録E: C++プログラミング言語の第3版の標準ライブラリ例外安全性付録に少し書いいます。PDFをダウンロードし、PDFリーダーで「init(」を検索します。コンストラクターと不変条件についてはセクション§E3.5に直接アクセスする必要があります。使用については少なくともセクション§E.3.5.1を読んでください。init()関数

Bjarneがそこにリストしているように、…

[…]別のinit()機能を持つことは、
[1]呼び出しを忘れるinit()(§10.2.3)、
[2]成功のテストを忘れるinit()、 [3]複数回
呼び出す、 [4]それがスローされる可能性があることを忘れる機会です例外であり、 [5]を呼び出す前にオブジェクトを使用します。 init()
init()
init()

Bjarneの議論は、本全体と同様に、初心者にとっては素晴らしいことだと思います。

ただし、2フェーズ構築の一般的な理由、つまり派生クラス固有の初期化をサポートする理由は、ここでのBjarneの図の一部ではなく、まったく言及されていないことに注意してください。これが、多くのGUIフレームワークでの2フェーズ初期化の理由です。OK単相初期化を備えたC++GUIフレームワークはいくつか存在しますが、ほとんどが教育的な問題であったことを証明しています。初期のC ++プログラマーは、C ++ RAIIについて知らなかったか、ライブラリユーザーが理解できるとは想定できませんでした。

于 2013-03-26T04:34:33.477 に答える
1

最善のことは、これらの種類の割り当てを完全に回避することです。多くの場合、インスタンスをクラス内に直接配置できます。本当にポインタが必要な場合は、unique_ptrとshared_ptrを使用して自動メモリ管理を行うことができます。

あなたの例では、これで問題ありません。

struct packet001
{
    obj001 p01;
    obj002 p02;
};

それらをポインタにする必要がある場合:

struct packet001
{
    packet001()
      : p01(new obj001),
        p02(new obj002)
    {
    }

    std::unique_ptr<obj001> p01;
    std::unique_ptr<obj002> p02;
};

メモリはデストラクタで自動的に解放され、構築中に例外が発生した場合、割り当て解除が適切に行われます。

于 2013-03-26T04:28:50.413 に答える
0

Ctorですべての例外をキャッチし、例外がCtor内に到着した場合は、適切にクリーンアップする必要がありますか?

于 2013-03-26T04:30:23.523 に答える
0

コンストラクターが何度も失敗する可能性があり、その失敗後もプログラムを実行し続けたい場合は、2フェーズ構築、またはコンストラクターのクリーンアップで指摘されている他のさまざまな手段を使用しました。たとえば、ファイル名がユーザーによって提供されたファイルを読み取るオブジェクトを構築しようとしています。そこでは、コンストラクターが何度も失敗する可能性があります。たとえば、ユーザー入力が悪い場合などです。

しかし、bad_alloc-うまく設計されたプログラムではまれなはずです。そして、メモリ割り当てが失敗した場合、正確に何をするつもりですか?あなたのC++プログラムは、おそらくその時点で運命づけられています。その時点でなぜメモリリークを心配するのですか?これで、悪い割り当てに遭遇した後でもプログラムが実行し続けることができるいくつかの反例、またはbad_allocsを回避するために凝った技術を採用しているプログラムを指摘できますが、あなたのプログラムはそれらの1つですか?

于 2013-03-26T04:40:47.033 に答える