19
struct Foo
{
    Foo(int i)
    {
        ptr = new int(i);
    }
    ~Foo()
    {
        delete ptr;
    }
    int* ptr;
};

int main()
{
    {
        Foo a(8);
        Foo b(7);
        a = b;
    }
    //Do other stuff
}

私が正しく理解していれば、コンパイラは自動的にの代入演算子メンバー関数を作成しますFoo。ただし、それはptrinの値を取り、bそれをに入れaます。によって割り当てられたメモリはa元々失われているようです。割り当てを行う前に電話をかけることはできましたがa.~Foo();、デストラクタを明示的に呼び出す必要はめったにないという話をどこかで聞きました。したがって、代わりに、r値をl値に割り当てる前に、左のオペランドFooのポインターを削除する代入演算子を作成するとします。intそのようです:

Foo& operator=(const Foo& other) 
{
    //To handle self-assignment:
    if (this != &other) {
        delete this->ptr;
        this->ptr = other.ptr;
    }
    return *this;
}

しかし、そうすると、スコープ外になるFoo aFoo b、両方のデストラクタが実行されて、同じポインタが2回削除されません(両方が同じものを指しているため)。

編集:

Anders Kを正しく理解している場合、これが適切な方法です。

Foo& operator=(const Foo& other) 
{
    //To handle self-assignment:
    if (this != &other) {
        delete this->ptr;
        //Clones the int
        this->ptr = new int(*other.ptr);
    }
    return *this;
}

ここで、が指しているをa複製し、それへの独自のポインターを設定します。おそらくこの状況では、とはsだけを含むため必要ではありませんでしたが、データメンバーがそうではなく、そうでない場合は、再割り当てが必要になる可能性があります。intbdeletenewintint*Bar*

編集2: 最良の解決策は、コピーアンドスワップのイディオムのようです。

4

3 に答える 3

12

これがメモリリークかどうか?
いいえ、そうではありません。

ほとんどの人がここでのポイントを逃しているようです。それで、ここに少し説明があります。

この回答の「いいえ、リークしません」という最初の応答は正しくありませんでしたが、ここ提案された解決策が、問題に対する唯一の最も適切な解決策です。


あなたの悩みの解決策は次のとおりです。

整数member(int *)へのポインターを使用せず、整数(int)のみを使用するために、ここで動的に割り当てられたポインターメンバーは実際には必要ありません。intasメンバーを使用して同じ機能を実現できます。
C ++では、使用する量newをできるだけ少なくする必要があることに注意してください。

何らかの理由で(コードサンプルでは確認できません)、動的に割り当てられたポインターメンバーが読み取られない場合は実行できません。

三つのルールに従う必要があります!


なぜ三つのルールに従う必要があるのですか?

三つのルールは次のように述べています。

クラスにどちらかが必要な場合

コピーコンストラクタ、代入 演算子、または デストラクタ

次に、 3つすべてが必要になる可能性があります。

クラスには独自の明示的なデストラクタが必要なので、明示的なコピーコンストラクタとコピー代入演算子も必要です。
クラスのコピーコンストラクターとコピー代入演算子は暗黙的であるため、これらも暗黙的にパブリックになります。つまり、クラス設計では、このクラスのオブジェクトをコピーまたは代入できます。これらの関数の暗黙的に生成されたバージョンは、動的に割り当てられたポインターメンバーの 浅いコピーのみを作成します。これにより、クラスが次のように公開されます。

  • メモリリーク&
  • ダングリングポインタ&
  • 二重割り当て解除の潜在的な未定義動作

これは基本的に、暗黙的に生成されたバージョンではうまくいかないことを意味します。独自のオーバーロードされたバージョンを提供する必要があります。これは、RuleofThreeが最初に言っていることです。

明示的に提供されたオーバーロードは、割り当てられたメンバーのディープコピーを作成する必要があるため、すべての問題を防ぎます。

代入演算子のコピーを正しく実装するにはどうすればよいですか?

この場合、コピー代入演算子を提供する最も効率的で最適化された方法は、次を使用することです。
コピーアンドスワップイディオム
@GManNickGの有名な回答は、それが提供する利点を説明するのに十分な詳細を提供します。


提案:

また、明示的なメモリ管理に負担をかける生のポインタよりも、クラスメンバーとしてスマートポインタを使用する方がはるかに優れています。スマートポインタが暗黙的にメモリを管理します。使用するスマートポインターの種類は、メンバーを対象とした存続期間所有権のセマンティクスによって異なり、要件に応じて適切なスマートポインターを選択する必要があります。

于 2012-05-26T05:01:50.367 に答える
10

これを処理する通常の方法は、ポインターが指すオブジェクトのクローンを作成することです。そのため、代入演算子を使用することが重要です。割り当て演算子が定義されていない場合、デフォルトの動作はmemcpyであり、両方のデストラクタが同じオブジェクトを削除しようとするとクラッシュが発生し、ptrがbで指していた以前の値が削除されないためメモリリークが発生します。

Foo a

         +-----+
a->ptr-> |     |
         +-----+

Foo b

         +-----+
b->ptr-> |     |
         +-----+

a = b

         +-----+
         |     |
         +-----+
a->ptr            
       \ +-----+
b->ptr   |     |
         +-----+

when a and b go out of scope delete will be called twice on the same object.

編集:ベンジャミン/アルスが正しく指摘したように、上記はこの特定の例を参照しているだけです、コメントで以下を参照してください

于 2012-05-26T05:01:43.130 に答える
1

提示されたコードの動作は未定義です。そのため、(予想どおりに)メモリリークが発生した場合、それはUBの考えられる兆候の1つにすぎません。また、バラク・オバマに怒りの脅迫状を送ったり、赤(またはオレンジ)の鼻のデーモンを吐き出したり、何もしなかったり、メモリリークがなかったかのように振る舞ったり、奇跡的に記憶を取り戻したりすることもできます。

解決策:の代わりにint*、を使用しますint

struct Foo
{
    Foo(int i): blah( i ) {}
    int blah;
};

int main()
{
    {
        Foo a(8);
        Foo b(7);
        a = b;
    }
    //Do other stuff
}

それはより安全で、より短く、はるかに効率的で、はるかに明確です。

この質問に対して提示された他の解決策は、客観的な尺度で上記を上回っていません。

于 2012-05-26T08:24:39.417 に答える