Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++에서 'delete this' 사용하기 – 동작 원리와 위험성 완벽 정리

delete 연산자는 변수가 차지하고 있는 저장 공간을 해제(deallocate)하는 데 사용되는 연산자입니다.

this 포인터는 비정적(non-static) 멤버 함수 내부에서만 접근할 수 있는 특수한 포인터로, 해당 멤버 함수를 호출한 객체의 주소를 가리킵니다. 쉽게 말해, this 포인터는 현재 객체 자신의 주소를 담고 있으며 클래스의 현재 객체를 가리킨다고 할 수 있습니다.

객체를 통해 멤버 함수를 호출할 때마다 컴파일러는 내부적으로 호출된 객체의 주소를 멤버 함수의 첫 번째 매개변수로 암묵적으로 전달하며, 이것이 바로 this 포인터입니다.

delete this는 안전할까?

일반적으로 delete this는 사용하지 않는 것이 좋습니다. 하지만 만약 사용한다면 다음 사항들을 반드시 고려해야 합니다.

1. new로 할당된 객체에만 유효합니다

delete 연산자는 오직 operator new로 할당된 객체에만 동작합니다. 즉, 객체가 new로 생성된 경우에만 delete this가 유효하며, 그렇지 않은 경우 동작은 정의되지 않습니다(undefined behavior).

class A {
    public:
    void fun() {
        delete this;
    }
};
int main() {
    /* 아래는 유효함 */
    A *ptr = new A;
    ptr->fun();
    ptr = NULL; // ptr을 NULL로 만들어 이후 접근을 방지

    /* 아래는 잘못된 코드: 정의되지 않은 동작 */
    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는 사용하지 마세요

가장 좋은 방법은 애초에 delete this를 사용하지 않는 것입니다. 멤버 함수 내부에서 this 포인터를 삭제하는 것은 잘못된 습관이며, 절대 그렇게 해서는 안 됩니다. 하지만 만약 이런 코드를 작성한다면 다음과 같은 상황이 발생할 수 있습니다.

  • 객체가 스택(stack)에 생성된 경우: 멤버 함수가 호출된 객체가 스택에 생성되었다면, this 포인터를 삭제하는 순간 프로그램이 크래시(crash)되거나 정의되지 않은 동작이 발생합니다.

  • 객체가 힙(heap)에 생성된 경우: 객체가 new 연산자를 통해 힙에 생성되었다면, 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 << "멤버에 접근하지 않는 함수" << std::endl;
}
int main() {
    Dummy * dummyPtr = new Dummy(5);
    dummyPtr->destroy();
    dummyPtr->displayText();
    return 0;
}

위 예제에서 destroy() 멤버 함수 내부에서 this 포인터를 삭제한 후에는 다음과 같은 차이가 발생합니다.

  • displayText()를 호출하는 것은 안전합니다. 이 함수는 어떤 멤버 변수도 접근하지 않기 때문입니다.

  • 반면 displayValue()를 호출하면 프로그램이 크래시됩니다. 이 함수는 이미 삭제된 this 포인터, 즉 댕글링 포인터(dangling pointer)를 통해 멤버 변수에 접근하기 때문입니다.

결론적으로, delete this는 C++에서 피해야 할 위험한 패턴입니다. 객체의 수명 관리는 스마트 포인터(std::unique_ptr, std::shared_ptr)나 RAII 패턴과 같은 안전한 방법으로 처리하는 것이 바람직합니다.