C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で「delete this」は使える?正しい条件とリスクをわかりやすく解説

C++におけるdelete演算子とthisポインタとは

delete演算子は、変数が確保していた記憶域(メモリ領域)を解放するために使用される演算子です。

thisポインタは、非staticメンバ関数の内部からのみアクセスできる特殊なポインタであり、そのメンバ関数を呼び出したオブジェクトのアドレスを指します。簡単に言えば、thisポインタは現在のオブジェクト、すなわちクラス自身のインスタンスを指し示しているのです。

オブジェクトを通じてメンバ関数を呼び出すたびに、コンパイラは密かに、呼び出し元オブジェクトのアドレスを第1引数としてメンバ関数へ渡しています。これこそがthisポインタの正体です。

「delete this」を使用する際の注意点

通常、delete演算子をthisポインタに対して使うべきではありません。理想的には避けるべき操作ですが、仮に使用する場合には、以下の重要なポイントを必ず考慮しなければなりません。

1. newで確保されたオブジェクトにのみ有効

delete演算子は、operator newによって割り当てられたオブジェクトに対してのみ機能します。オブジェクトがnewで生成されている場合は「delete this」が可能ですが、それ以外の場合の動作は未定義です。

class A {
public:
    void fun() {
        delete this;
    }
};
int main() {
    /* 以下は有効 */
    A *ptr = new A;
    ptr->fun();
    ptr = NULL; // ptrをNULLにして、ptr経由のアクセスを防ぐ
    /* 以下は無効:未定義動作 */
    A a;
    a.fun();
    getchar();
    return 0;
}

2. 削除後のメンバへのアクセスは禁止

一度「delete this」を実行した後は、削除されたオブジェクトのいかなるメンバにもアクセスしてはいけません。

#include<iostream>
using namespace std;
class A {
    int x;
public:
    A() { x = 0; }
    void fun() {
        delete this;
        /* 無効:未定義動作 */
        cout<<x;
    }
};

「delete this」を実行した場合に起こること

メンバ関数内でthisポインタを削除することは誤った使い方であり、決して行うべきではありません。最良の選択は、そもそも「delete this」を一切使わないことです。しかし万一行ってしまった場合、オブジェクトの生成場所に応じて以下のような結果になります。

  • スタック上に生成されたオブジェクトの場合: メンバ関数を呼び出したオブジェクトがスタック上に作成されていた場合、thisポインタを削除すると、アプリケーションがクラッシュするか、未定義動作が発生します。

  • ヒープ上に生成されたオブジェクトの場合: オブジェクトがnew演算子によってヒープ上に作成されていた場合、thisポインタの削除によってオブジェクトは破壊されます。この瞬間にはアプリケーションはクラッシュしませんが、その後に何らかのメンバ関数がこのオブジェクト経由でメンバ変数へアクセスしようとすると、アプリケーションはクラッシュします。

具体例で見る「delete this」の挙動

#include <iostream>
class Dummy {
    int m_value;
public:
    Dummy(int val) :
    m_value(val)
    {}
    void destroy();
    void displayValue();
    void displayText();
};
void Dummy::destroy() {
    delete this;
}
void Dummy::displayValue() {
    std::cout << this->m_value << std::endl;
}
void Dummy::displayText() {
    std::cout << "Not accessing any member" << std::endl;
}
int main() {
    Dummy * dummyPtr = new Dummy(5);
    dummyPtr->destroy();
    dummyPtr->displayText();
    return 0;
}

destroy()メンバ関数内でthisポインタを削除した後、displayText()の呼び出しは安全です。これは、displayText()がどのメンバにもアクセスしないためです。一方、displayValue()を呼び出すとアプリケーションはクラッシュします。理由は、ダングリングポインタ(既に削除されたthisポインタ)を通じてメンバ変数へアクセスしようとするからです。

  1. C++でツリーノードを削除する:合計値が0の部分木を除去するアルゴリズム

    問題概要根がノード0であるような木構造を考えます。この木には、次の情報が与えられています。ノードの総数:nodesi番目のノードの値:value[i]i番目のノードの親:parent[i]求めたいのは、「ノードの値の合計が0になる部分木」をすべて削除した後、木に残っているノードの個数です。たとえば、下図のような木を考えてみましょう。ノードは全部で7つありますが、出力は2になります。これは、値が0であるノード3を根とする部分木と、ノード2を根とする部分木(4 + (-2) + (-1) + (-1) = 0)が削除対象となり、最終的に残るのがノード0とノード1だけだからです。解法の考え方この問題

  2. C++で二分探索木(BST)からノードを削除する方法

    二分探索木(BST:Binary Search Tree)が与えられます。ここで1つのキー k を受け取り、そのキー k をBSTから削除して、更新されたBSTを返すことを考えます。 例えば、次のような木があるとします。 そして、削除するキーが k = 3 の場合、出力される木は次のようになります。 アルゴリズムの考え方 この問題を解くために、まず「ルートノードを削除する」処理を担当する補助メソッド deleteRoot() を定義します。このメソッドは以下のように動作します。 root が null の場合は、null を返します。 root に右部分木が存在しない場合は、roo