5

デストラクタに関連するいくつかの疑問があります。

class cls
{
    char *ch;
public:
    cls(const char* _ch)
    {
        cout<<"\nconstructor called";
        ch = new char[strlen(_ch)];
        strcpy(ch,_ch);
    }
    ~cls()
    {
        //will this destructor automatically delete char array ch on heap?
            //delete[] ch; including this is throwing heap corruption error
    }
    void operator delete(void* ptr)
    {
        cout<<"\noperator delete called";
        free(ptr);
    }
};
int main()
{
    cls* cs = new cls("hello!");
    delete(cs);
    getchar();
}

また、デストラクタは削除時に自動的に呼び出されるため、すべてのロジックをデストラクタで記述できるのに、なぜ明示的な削除が必要なのですか?

演算子の削除とデストラクタに関して非常に混乱しており、特定の使用法を理解できませんでした。詳しい説明大変参考になります。

編集: 答えに基づく私の理解:この特定のケースでは、デフォルトのデストラクタが char ポインタを破損するため、最初に char 配列を明示的に削除する必要があります。そうしないと、メモリ リークが発生します。間違っている場合は修正してください。

4

7 に答える 7

8

デフォルトのデストラクタは、メンバ変数によって使用されるメモリの割り当てを解除します (つまり、メンバ ポインタch自体が存在しなくなります) が、メンバ ポインタによって参照されるメモリの割り当ては自動的に解除されません。したがって、あなたの例にはメモリリークがあります。

于 2013-07-14T18:33:20.507 に答える
6

delete関数ではありません (ただし、オーバーロードできます)。はい、割り当て解除のロジックをデストラクタに記述することをお勧めします。しかし、解放がデストラクタによって自動的に実行されると仮定するのは正しくありません。問題は、デストラクタがオブジェクトの存続期間の終わりに呼び出されることですが、それが何を行うかは、作成したコードに依存します。つまり、デストラクタ内delete[]で呼び出す必要があります。ch

~cls()
{
    delete[] ch;
    ch = nullptr;
}

chさらに、ヒープ破損エラーは、ヌルバイトの初期化に十分な余地を残さなかったという事実から来ていると思います\0。member-initializer リストも使用する必要があります。コンストラクターを次のように変更します。

cls(const char* _ch) : ch(new char[1+strlen(_ch)])
{
    std::cout << "\nconstructor called";
    std::strcpy(ch, _ch);
}

コードに加えることができる多くの改善があります。つまり、Rule Of Threestd::stringを使用し、それに従うことです。コードにはオーバーロードも必要ありません。スタックを割り当てる必要があります:operator delete()cs

#include <iostream>
#include <string>

class cls
{
    std::string ch;
    public:
        cls() { std::cout << "default constructor called\n"; }

        cls(std::string _ch) : ch(_ch)
        {
            std::cout << "constructor called\n";
        }

        cls(cls const& other) : ch(other.ch)
        {
            std::cout << "copy-constructor called\n";
        }

        ~cls() { std::cout << "destructor called\n"; }
};

int main()
{
    cls cs("hello!");

    std::cin.get();

} // <-- destructor gets called automatically for cs
于 2013-07-14T18:43:29.680 に答える
3

いいえ、デストラクタは魔法deleteのようにメモリが指すことはありませんch。呼び出した場合(コンストラクターで呼び出した場合)、適切なタイミングでnew呼び出す必要もあります。delete

デストラクタは、オブジェクトが破棄されるときに実行されます。これは、自動オブジェクト (つまり、スタックに割り当てられたもの) がスコープ外に出ようとしている場合、または明示的deleteに new で割り当てられたオブジェクトの場合です。

一般に、newメモリを割り当てる方法として、コンストラクタをそのメモリを取得してオブジェクトにする方法、デストラクタをオブジェクトを取得して破棄し、メモリのチャンクを残してそのチャンクを取得する方法とdelete考えてください。メモリとその割り当てを解除します。

便宜上new、コンパイラを呼び出すと、要求したメモリが割り当てられた後にコンストラクタが呼び出されdelete、コンパイラを呼び出すと、デストラクタが自動的に呼び出されます。

バッファオーバーフローがあるため、ヒープ破損エラーが発生していますstrcpy。追加するヌル終端バイトにスペースを割り当てていません。

C 文字列は一連のバイトであり、その後にヌル バイトが続くことを思い出してください。これは、長さ 5 の文字列を格納するには、実際には 6 バイトが必要であることを意味します。

std::stringまた、非常に堅牢でフル機能の実装が既に利用可能である場合は、C スタイルの配列の代わりに使用して問題を解決し、エラーが発生しやすいコードを記述する必要がないことも忘れないでください。

宿題/学習課題の顕著な例外を除いて、を使用する代わりに C スタイルの文字列を直接実装する必要がある状況はほとんどありませんstd::string

同じことが (多少厳密ではありませんが) 動的配列一般にも当てはまります。std::vector代わりに使用してください。

于 2013-07-14T18:40:58.810 に答える
2

特定のクラスの削除演算子をオーバーライドしても意味がありません。それが、グローバル削除演算子の目的です。

あなたがすべきことは、デストラクタの ch に対する delete[] です。delete オペレータは、クラスのインスタンスを格納するために直接割り当てられたメモリの割り当てを解除するだけなので、これは明示的に行う必要があります。コンストラクターでさらにメモリを割り当てているため、破棄時に解放する必要があります。

経験則として、コンストラクタとデストラクタは対称的にコーディングする必要があると想定できます。コンストラクターのすべての new について、デストラクタで削除する必要があります。

ところで、C++ アロケーター (new/delete) と C アロケーター (malloc/free) を混在させてはいけません。C++ で割り当てたものは、C++ で解放する必要があり、その逆も同様です。

于 2013-07-14T18:43:10.850 に答える
2

C++ のメモリ容量はRAIIに基づいています。つまり、変数の有効期間が終了すると、デストラクタが呼び出されます。

例えば:

class Foo
{
public:
   Foo() { cout << "Constructor!!!" << endl; }
   ~ Foo() { cout << "Destructor!!!" << endl; }
};

int main()
{
   Foo my_foo_instance;
}

版画:

コンストラクタ!!!
デストラクタ!!!

コンストラクターは の初期化my_foo_instance(宣言時) で呼び出され、デストラクタは の有効期間がmy_foo_instance終了するとき (つまり の最後main()) に呼び出されるためです。

また、このルールは、クラス属性を含むあらゆるコンテキストで機能します。

class Foo1
{
public:
   Foo1() { cout << "Foo1 constructor!!!" << endl; }
   ~ Foo1() { cout << "Foo1 destructor!!!" << endl; }
};

class Foo2
{
private:
    Foo1 foo1_attribute;
public:
   Foo2() { cout << "Foo2 constructor!!!" << endl; }
   ~ Foo2() { cout << "Foo2 destructor!!!" << endl; }
};

int main()
{
   Foo2 my_foo2_instance;
}

版画:

Foo1 コンストラクター!!!
Foo2 コンストラクター!!!
Foo2 デストラクタ!!!
Foo1 デストラクタ!!!

プログラムのトレースは次のとおりです。

  • メインスタート
  • の初期化my_foo2_instance: Foo2 コンストラクターの呼び出し
  • まず、Foo2 はその属性を初期化します: Foo1 コンストラクターへの呼び出し
  • Foo1 には属性がないため、Foo1 はそのコンストラクタ本体を実行します。cout << "Foo1 constructor" << endl;
  • 属性の初期化後、Foo2 はそのコンストラクタ本体を実行します。cout << "Foo2 constructor" << endl;
  • メイン スコープの終了、つまりmy_foo2_instanceライフタイムの終了: Foo2 デストラクタへの呼び出し
  • Foo2 デストラクタはその本体を実行します:cout << "Foo2 destructor" << endl;
  • デストラクタの後、Foo2 の属性の有効期間が終了します。そのため: Foo1 デストラクタを呼び出します
  • Foo1 デストラクタはその本体を実行します:cout << "Foo1 destructor" << endl;
  • デストラクタの後、Foo1 の属性の有効期間が終了します。しかし、Foo1 には属性がありません。

しかし忘れているのは、ポインタは基本型であるため、デストラクタがないことです。ポインタが指しているオブジェクトを破壊する (つまり、指し示すオブジェクトの寿命を確定する) にはdelete、デストラクタ本体で use 演算子を使用します。

于 2013-07-14T18:54:11.653 に答える
1

デストラクタは、何も割り当てを解除しません。デストラクタは、クラス サブオブジェクトのデストラクタを暗黙的に呼び出すだけで、デストラクタの本体に入れたコードを実行します。あなたの場合、サブオブジェクトchは生のポインター型であるため、デストラクタはありません。したがって、あなたの場合は何も行われません。メモリを割り当てたのはあなたなので、それを解放する責任があるのはあなたです。delete[] ch要するに、はい、デストラクタでそれが必要です。

そのメモリの割り当てを自動的に解除する場合は、生のポインターの代わりにスマート ポインター クラスを使用します。その場合、クラスのデストラクタはスマート ポインタ サブオブジェクトのデストラクタを自動的に呼び出し、メモリの割り当てを解除します。あなたの特定の例ではstd::string、文字列をクラスオブジェクト内に格納するために使用することをお勧めします。

あなたの場合のヒープの破損は、文字列に十分なメモリを割り当てていないために発生します。これにより、範囲外の書き込みが発生しstrcpyます。そのはず

ch = new char[strlen(_ch) + 1];
strcpy(ch,_ch);

終端のゼロ文字には余分なスペースが必要です。

于 2013-07-14T18:45:43.040 に答える