53
#include <queue>
using namespace std;

class Test{
    int *myArray;

        public:
    Test(){
        myArray = new int[10];
    }

    ~Test(){
        delete[] myArray;
    }

};


int main(){
    queue<Test> q
    Test t;
    q.push(t);
}

これを実行すると、「ダブルフリーまたは破損」というランタイムエラーが発生します。デストラクタコンテンツ(delete)を削除すると、正常に機能します。どうしたの?

4

6 に答える 6

87

C++でのオブジェクトのコピーについて話しましょう。

Test t;は、整数の新しい配列を割り当てるデフォルトのコンストラクターを呼び出します。これは問題ありません、そしてあなたの期待される振る舞い。

tを使用してキューにプッシュすると、問題が発生しますq.push(t)。Java、C#、またはその他のほとんどすべてのオブジェクト指向言語に精通している場合は、以前に作成したオブジェクトがキューに追加されることを期待できますが、C++はそのようには機能しません。

std::queue::pushメソッドを見ると、キューに追加される要素が「xのコピーに初期化されている」ことがわかります。Testこれは実際には、コピーコンストラクターを使用して元のオブジェクトのすべてのメンバーを複製して新しいを作成するまったく新しいオブジェクトですTest

C ++コンパイラは、デフォルトでコピーコンストラクタを生成します。これは非常に便利ですが、ポインタメンバーに問題が発生します。int *myArrayあなたの例では、それは単なるメモリアドレスであることを忘れないでください。の値がmyArray古いオブジェクトから新しいオブジェクトにコピーされると、メモリ内の同じ配列を指す2つのオブジェクトが作成されます。これは本質的に悪いことではありませんが、デストラクタは同じアレイを2回削除しようとするため、「ダブルフリーまたは破損」ランタイムエラーが発生します。

どうすれば修正できますか?

最初のステップは、あるオブジェクトから別のオブジェクトにデータを安全にコピーできるコピーコンストラクターを実装することです。簡単にするために、次のようになります。

Test(const Test& other){
    myArray = new int[10];
    memcpy( myArray, other.myArray, 10 );
}

これで、テストオブジェクトをコピーするときに、新しい配列が新しいオブジェクトに割り当てられ、配列の値もコピーされます。

しかし、私たちはまだ完全に問題を抱えているわけではありません。同様の問題を引き起こす可能性のあるコンパイラが生成する別の方法があります-割り当て。違いは、割り当てでは、メモリを適切に管理する必要がある既存のオブジェクトがすでに存在することです。基本的な代入演算子の実装は次のとおりです。

Test& operator= (const Test& other){
    if (this != &other) {
        memcpy( myArray, other.myArray, 10 );
    }
    return *this;
}

ここで重要なのは、他の配列からこのオブジェクトの配列にデータをコピーし、各オブジェクトのメモリを分離しておくことです。自己割り当てのチェックもあります。そうしないと、自分自身から自分自身にコピーすることになり、エラーがスローされる可能性があります(何をすべきかわからない)。より多くのメモリを削除して割り当てていた場合、自己割り当てチェックにより、コピーする必要のあるメモリを削除できなくなります。

于 2012-12-28T02:37:51.047 に答える
17

問題は、クラスにマネージドRAWポインターが含まれているが、3つのルール(C ++ 11では5つ)が実装されていないことです。その結果、コピーが原因で(予想どおり)二重削除が発生します。

学習している場合は、三つのルールを実装する方法を学ぶ必要があります。しかし、それはこの問題の正しい解決策ではありません。独自の内部コンテナを管理するのではなく、標準のコンテナオブジェクトを使用する必要があります。正確なコンテナは、実行しようとしていることによって異なりますが、std :: vectorが適切なデフォルトです(最適でない場合は、後の単語を変更できます)。

#include <queue>
#include <vector>

class Test{
    std::vector<int> myArray;

    public:
    Test(): myArray(10){
    }    
};

int main(){
    queue<Test> q
    Test t;
    q.push(t);
}

標準のコンテナを使用する必要がある理由はですseparation of concerns。クラスは、ビジネスロジックまたはリソース管理のいずれか(両方ではない)に関係している必要があります。プログラムTestに関する状態を維持するために使用しているクラスがあるとすると、それはビジネスロジックであり、リソース管理を行うべきではありません。一方Test、配列を管理することになっている場合は、おそらく標準ライブラリ内で利用できるものについてさらに学ぶ必要があります。

于 2012-12-28T06:02:52.930 に答える
4

最初のデストラクタがオブジェクトq用であるため、ダブルフリーまたは破損が発生しています。この場合、 newによって割り当てられたメモリは解放されます。次回、デストラクタがオブジェクトtに対して呼び出されるときに、メモリはすでに解放されています(qに対して実行されます)。デストラクタの場合delete[]myArray; 実行すると、ダブルフリーまたは破損がスローされます。その理由は、両方のオブジェクトが同じメモリを共有しているため、上記の回答で述べたように、\ copy、代入、および等号演算子を定義するためです。

于 2012-12-28T02:30:20.547 に答える
3

コピーコンストラクタ、代入、演算子を定義する必要があります。

class Test {
   Test(const Test &that); //Copy constructor
   Test& operator= (const Test &rhs); //assignment operator
}

キューにプッシュされたコピーは、元のメモリと同じメモリを指しています。最初のものが破壊されると、それはメモリを削除します。2つ目は、同じメモリを破棄して削除しようとします。

于 2012-12-28T02:23:14.973 に答える
0

削除する前にnullをチェックして、次のようにすることもできます。

if(myArray) { delete[] myArray; myArray = NULL; }

または、次のように安全な方法ですべての削除操作を定義できます。

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if(p) { delete (p); (p) = NULL; } }
#endif

#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p) = NULL; } }
#endif

その後、

SAFE_DELETE_ARRAY(myArray);
于 2015-08-18T07:23:09.840 に答える
-2

ええと、デストラクタはdelete []ではなくdeleteを呼び出すべきではありませんか?

于 2012-12-28T02:45:09.667 に答える