삭제가 허용됩니까?


232

delete this;delete-statement가 클래스의 해당 인스턴스에서 실행될 마지막 명령문 인 경우 허용 됩니까? 물론 - this포인터가 나타내는 객체 가 new생성 된 것이 확실합니다 .

나는 이것에 대해 생각하고있다 :

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

내가 할 수 있습니까?


13
주요 문제는 delete this클래스와 해당 클래스의 객체를 만드는 데 사용 된 할당 방법 사이에 긴밀한 연결을 만든 경우 입니다. OOP에서 가장 근본적인 것은 호출자가하는 일을 모르거나 신경 쓰지 않는 자율적 인 클래스를 만드는 것이므로 OO 디자인은 매우 열악합니다. 따라서 적절하게 설계된 클래스는 클래스가 어떻게 할당되었는지 알거나 신경 쓰지 않아야합니다. 어떤 이유로 든 특별한 메커니즘이 필요하다면 더 나은 디자인은 실제 클래스 주위에 래퍼 클래스를 사용하고 래퍼가 할당을 처리하게하는 것입니다.
Lundin

에서 삭제할 수 없습니까 setWorkingModule?
지미 T.

답변:


238

C ++ FAQ Lite에는 특별히이 항목이 있습니다.

나는이 인용문을 잘 요약한다고 생각합니다.

조심하는 한 개체가 자살하는 것이 좋습니다 (삭제).


15
해당 FQA에도 유용한 의견이 있습니다. yosefk.com/c++fqa/heap.html#fqa-16.15
Alexandre C.

1
안전을 위해 원래 객체에서 전용 소멸자를 사용하여 스택에 또는 배열 또는 벡터의 일부로 구성되지 않았는지 확인할 수 있습니다.
Cem Kalyoncu

'신중한'정의
CinCout

3
'조심'은 링크 된 FAQ 기사에 정의되어 있습니다. (FQA 링크는 대부분 거의 모든 것과 마찬가지로 C ++이 얼마나 나쁜지를
나타냅니다

88

예, delete this;객체가 동적으로 할당되고 객체가 파괴 된 후에는 절대로 객체를 사용하려고 시도하지 않는 한 결과를 정의했습니다. 수년에 걸쳐 delete this;다른 포인터를 삭제하는 대신 표준에 대해 구체적으로 말하는 것에 대해 많은 질문이 제기되었습니다 . 그것에 대한 대답은 상당히 짧고 간단합니다. 그것은 단지 말한다 delete'의 피연산자는 객체 또는 객체의 배열에 대한 포인터를 지정하는 식이어야합니다. 메모리를 해제하기 위해 호출하는 할당 해제 기능을 알아내는 방법과 같은 것들에 대해 상당히 자세히 설명하지만 delete(§ [expr.delete]) 의 전체 섹션에서 delete this;특별히 언급하지는 않습니다 . destrucors 섹션에 언급되어 있습니다delete this 한 곳에서 (§ [class.dtor] / 13) :

가상 소멸자의 정의 시점 (암시 적 정의 (15.8) 포함)에서, 비 배열 할당 해제 함수는 표현식이 소멸자 클래스의 가상이 아닌 소멸자에 나타나는 것을 삭제하는 것처럼 결정됩니다 (8.3.5 참조). ).

그것은 표준 delete this;이 타당 하다고 생각한다는 생각을지지하는 경향이 있습니다. 만약 그것이 틀렸다면 그 유형은 의미가 없습니다. 그것이 delete this;내가 아는 한, 표준이 언급 한 유일한 곳 입니다.

어쨌든, 일부 delete this는 불쾌한 해킹을 고려 하고 들어야 할 사람은 피해야한다고 말합니다. 일반적으로 인용되는 문제 중 하나는 클래스의 객체가 동적으로 만 할당되는 것이 어렵다는 것입니다. 다른 사람들은 그것을 완전히 합리적인 관용구로 생각하고 항상 사용합니다. 개인적으로 저는 중간에 있습니다. 거의 사용하지 않지만, 업무에 적합한 도구 인 것 같으면 주저하지 마십시오.

이 기술을 사용하는 첫 번째 시간은 거의 전적으로 자신의 삶을 가진 개체를 사용하는 것입니다. James Kanze가 인용 한 한 가지 사례는 전화 회사에서 근무한 청구 / 추적 시스템입니다. 전화를 걸 때 무언가가이를 기록하고 phone_call객체를 만듭니다 . 이 시점부터 phone_call객체는 전화 통화의 세부 사항을 처리합니다 (전화를 걸 때 연결, 전화를 걸 때 말하는 데이터베이스에 항목 추가, 전화 회의를하는 경우 더 많은 사람을 연결할 수 있음). 마지막으로 전화를 건 사람이 전화를 끊으면 해당 phone_call객체는 최종 부기 작업을 수행합니다 (예 : 전화를 끊은 시간을 말하도록 데이터베이스에 항목을 추가하여 전화 시간을 계산할 수 있음). 평생phone_call객체는 첫 번째 사람이 전화를 시작한 시간과 마지막 사람이 전화를 떠날 때를 기반으로합니다. 시스템의 나머지 부분에서 볼 때 기본적으로 완전히 임의적이므로 코드의 어휘 범위에 연결할 수 없습니다 또는 그 순서에 따라

이러한 종류의 코딩이 얼마나 신뢰할 수 있는지에 관심이있는 사람은 유럽에서 거의 모든 지역을 통해 전화를 걸면 코드로 (적어도 부분적으로) 처리 될 가능성이 큽니다. 정확히이 작업을 수행합니다.


2
고마워, 내 기억에 어딘가에 넣을 것이다. 생성자와 소멸자를 개인으로 정의하고 정적 팩토리 메소드를 사용하여 이러한 객체를 생성한다고 가정합니다.
Alexandre C.

@Alexandre : 어쨌든 대부분의 경우 그렇게 할 것입니다. 그가 작업하고있는 시스템의 모든 세부 사항에 가까운 곳은 알 수 없으므로 확실하게 말할 수는 없습니다.
Jerry Coffin

메모리가 할당 된 방식의 문제를 해결하는 방법은 bool selfDelete멤버 변수에 할당되는 생성자에 매개 변수 를 포함시키는 것 입니다. 물론 이것은 프로그래머가 밧줄을 올가미에 묶을 수 있도록 충분한 양의 밧줄을 건네주는 것을 의미하지만, 메모리 누수보다 선호하는 것을 알았습니다.
MBraedley

1
@MBraedley : 나는 똑같이했지만 나에게 kludge처럼 보이는 것을 피하는 것을 선호합니다.
Jerry Coffin

관심이있는 사람이라면 ... 정확히 수행하는 코드로 (적어도 부분적으로) 처리 될 가능성이 큽니다 this. 예, 코드는 정확히에 의해 처리됩니다 this. ;)
Galaxy

46

그것이 당신을 두려워한다면, 완전히 합법적 인 해킹이 있습니다.

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

나는 delete this관용적 인 C ++ 이라고 생각 하며 이것을 호기심으로 만 제시합니다.

이 구문이 실제로 유용한 경우가 있습니다. 오브젝트에서 멤버 데이터가 필요한 예외를 처리 한 후 오브젝트를 삭제할 수 있습니다. 던지기가 끝날 때까지 개체는 계속 유효합니다.

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

참고 : C ++ 11보다 오래된 컴파일러를 사용하는 경우 std::auto_ptr대신 대신 사용할 수 있습니다 std::unique_ptr.


c ++ 11을 사용하여 컴파일 할 수 없습니다. 특별한 컴파일러 옵션이 있습니까? 또한 this 포인터의 이동이 필요하지 않습니까?
Owl

@Owl은 무슨 뜻인지 잘 모르겠습니다. ideone.com/aavQUK . 만들기 unique_ptr에서 다른 것은 unique_ptr 아니지만 원시 포인터에서, 이동이 필요합니다. C ++ 17에서 변경된 사항이 없다면?
Mark Ransom

Ahh C ++ 14, 이것이 바로 그 이유입니다. dev 상자에서 c ++를 업데이트해야합니다. 나는 최근에 등장한 젠투 시스템에서 오늘 밤 다시 시도 할 것입니다!
Owl

25

C ++가 설계된 이유 중 하나는 코드를 쉽게 재사용 할 수 있도록하기위한 것입니다. 일반적으로 C ++은 클래스가 힙, 배열 또는 스택에서 인스턴스화되는지 여부에 관계없이 작동하도록 작성해야합니다. "삭제"는 단일 인스턴스가 힙에 정의 된 경우에만 작동하기 때문에 매우 나쁜 코딩 방법입니다. 그리고 대부분의 개발자가 힙을 정리하기 위해 일반적으로 사용하는 또 다른 delete 문이없는 것이 좋습니다. 이렇게하면 향후 유지 관리 프로그래머가 delete 문을 추가하여 잘못 인식 된 메모리 누수를 치료하지 않을 것이라고 가정합니다.

현재 계획이 힙에 단일 인스턴스 만 할당한다는 것을 미리 알고 있다고해도, 운이 좋은 개발자가 나중에 와서 스택에 인스턴스를 작성하기로 결정하면 어떻게됩니까? 또는 클래스의 특정 부분을 잘라서 스택에 사용하려는 새 클래스에 붙여 넣으면 어떻게됩니까? 코드가 "삭제"에 도달하면 코드가 사라지고 삭제되지만 개체가 범위를 벗어나면 소멸자를 호출합니다. 그러면 소멸자가 다시 삭제하려고 시도한 후 호스를칩니다. 과거에는 이와 같은 작업을 수행하면 프로그램뿐만 아니라 운영 체제와 컴퓨터를 재부팅해야합니다. 어쨌든 이것은 권장되지 않으며 거의 ​​항상 피해야합니다. 나는 필사적이고 진지하게 회개해야했고


7
+1. 왜 당신이 다운 보트를 받았는지 이해할 수 없습니다. "클래스가 힙, 배열 또는 스택에서 인스턴스화되는지 여부에 관계없이 작동하도록 C ++을 작성해야합니다."
Joh

1
당신은 개체를 삭제하는 특별한 클래스 자체를 삭제 할 개체를 래핑하고 자신을, 그리고 스택 할당을 방지하기 위해이 기술을 사용할 수 있습니다 stackoverflow.com/questions/124880/... 정말이 없을 때 시간이있다 실행 가능한 대안. 방금이 기술을 사용하여 DLL 함수로 시작된 스레드를 자동 삭제했지만 스레드가 끝나기 전에 DLL 함수가 반환되어야합니다.
Felix Dombek

누군가 코드로 복사하여 붙여 넣는 방식으로 코드를 잘못 사용하는 방식으로 프로그래밍 할 수 없습니다.
Jimmy T.

22

허용되지만 (그 후에 객체를 사용하지 마십시오) 실제로는 그러한 코드를 작성하지 않습니다. 나는 그 생각 delete this이라고 만 기능에 나타납니다 release또는 Release과 외모처럼 : void release() { ref--; if (ref<1) delete this; }.


내 모든 프로젝트에서 정확히 한 번입니다 ... :-)
cmaster-복원 monica

15

구성 요소 개체 모델 (COM)에서 delete this구성은 Release필요한 개체를 해제 할 때마다 호출 되는 메서드 의 일부일 수 있습니다 .

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}

8

이것은 참조 횟수 개체의 핵심 관용구입니다.

참조 카운팅은 강력한 결정 론적 가비지 콜렉션의 형태로, 객체가 '스마트 한'포인터 등에 의존하는 대신 객체가 자신의 수명을 관리하도록합니다. 기본 개체는 "참조"스마트 포인터를 통해서만 액세스되며, 포인터는 실제 개체에서 멤버 정수 (참조 카운트)를 증가시키고 감소시킵니다.

마지막 참조가 스택에서 떨어지거나 삭제되면 참조 카운트가 0이됩니다. 그러면 객체의 기본 동작은 가비지 수집에 대한 "삭제"호출입니다. 작성한 라이브러리는 기본 클래스에서 보호 된 가상 "CountIsZero"호출을 제공하므로 캐싱과 같은 경우이 동작을 재정의 할 수 있습니다.

이것을 안전하게 만드는 비결은 사용자가 문제의 객체 생성자에 액세스하는 것을 허용하지 않고 (보호하도록) 대신 "정적 참조 CreateT (...)"와 같은 일부 정적 멤버를 FACTORY라고 부르는 것입니다. 그렇게하면 항상 일반 "새"로 작성되었으며 사용할 수있는 원시 포인터가 없다는 것을 알고 있으므로 "삭제"가 발생하지 않습니다.


왜 모든 할당이 수행되고 그 클래스가 할당 된 객체의 모든 참조 카운팅을 처리 할 수있는 인터페이스 인 (단일) 클래스 "할당 자 / 가비지 수집기"를 가질 수 없습니까? 객체 자체가 가비지 수집 작업을 방해하지 않고 지정된 목적과 완전히 관련이없는 것입니다.
Lundin

1
소멸자를 보호하여 객체의 정적 및 스택 할당을 금지 할 수도 있습니다.
Game_Overture

7

그렇게 할 수 있습니다. 그러나 이에 할당 할 수 없습니다. 따라서 "내가보기를 바꾸고 싶다"고 말한 이유는 매우 의심 스럽다. 내 의견으로는 더 나은 방법은 뷰를 유지하는 객체가 해당 뷰를 대체하는 것입니다.

물론, 당신은 RAII 객체를 사용하고 있으므로 실제로 delete를 전혀 호출 할 필요가 없습니다 ... 맞습니까?


4

이것은 오래되고 대답 된 질문이지만 @Alexandre는 "왜 이것을 원할까요?"라고 물었고, 오늘 오후에 고려할 사용법의 예를 제공 할 수 있다고 생각했습니다.

레거시 코드. 맨 끝에 삭제 obj와 함께 알몸 포인터 Obj * obj를 사용합니다.

불행히도 때로는 종종 개체를 더 오래 살리기 위해 필요합니다.

참조 카운트 스마트 포인터로 만드는 것을 고려하고 있습니다. 그러나 모든 곳 에서 사용한다면 변경해야 할 코드 가 많이ref_cnt_ptr<Obj> 있습니다. 그리고 벌거 벗은 Obj *와 ref_cnt_ptr을 혼합하면 Obj *가 아직 살아 있더라도 마지막 ref_cnt_ptr이 사라질 때 암시 적으로 삭제 된 객체를 얻을 수 있습니다.

그래서 explicit_delete_ref_cnt_ptr을 만드는 것에 대해 생각하고 있습니다. 즉, 삭제는 명시적인 삭제 루틴에서만 수행되는 참조 카운트 포인터입니다. 기존 코드가 객체의 수명을 알고있는 곳과 객체를 더 오래 살리는 새로운 코드에서 사용하십시오.

explicit_delete_ref_cnt_ptr이 참조 될 때 참조 카운트를 늘리거나 줄입니다.

그러나 explicit_delete_ref_cnt_ptr 소멸자에서 참조 횟수가 0으로 표시되면 해제되지 않습니다.

명시적인 삭제와 유사한 작업에서 참조 횟수가 0 인 경우에만 해제됩니다. 예를 들면 다음과 같습니다.

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

좋아, 그런 것. 참조 카운트 포인터 유형이 rc'ed ptr 소멸자에서 가리키는 객체를 자동으로 삭제하지 않는 것은 조금 특이합니다. 그러나 이것이 벌거 벗은 포인터와 rc'ed 포인터를 조금 더 안전하게 혼합하는 것처럼 보입니다.

그러나 지금까지 이것을 삭제할 필요는 없습니다.

그러나 그것은 나에게 일어났다 : 포인트가 가리키는 객체가 참조 카운트되고 있음을 알고 있다면, 예를 들어 카운트가 객체 내부 (또는 다른 테이블)에있는 경우, delete_if_rc0 루틴은 (스마트) 포인터가 아닌 pointee 객체.

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

실제로, 그것은 멤버 메소드 일 필요는 없지만 무료 기능이 될 수 있습니다.

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(BTW, 코드가 옳지 않다는 것을 알고 있습니다. 세부 사항을 모두 추가하면 코드가 읽기 어려워 져서 이렇게 남겨 둡니다.)


0

객체가 힙에있는 한이 방법은 유효합니다. 객체는 힙 전용이어야합니다. 그렇게하는 유일한 방법은 소멸자를 보호하는 것입니다.이 방법으로 delete는 class에서만 호출 될 수 있으므로 삭제를 보장하는 메소드가 필요합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.