소멸자를 수동으로 호출하는 것이 항상 잘못된 디자인의 신호입니까?


84

나는 생각했다 : 그들은 소멸자를 수동으로 호출한다면-당신은 뭔가 잘못하고 있다고 말한다. 그러나 항상 그렇습니까? 반례가 있습니까? 수동으로 호출해야하는 상황이나 피하는 것이 어렵거나 불가능하거나 비현실적인 상황입니까?


다시 호출하지 않고 dtor를 호출 한 후 개체를 할당 해제하려면 어떻게해야합니까?
ssube

2
@peachykeen : 배치 new를 호출 하여 이전 객체 대신 새 객체를 초기화합니다. 일반적으로 좋은 생각은 아니지만 들어 본 적이 없습니다.
D.Shawley 2013 년

14
용의자와 함께 사양에서 직접 오지 않는 "항상"및 "절대"라는 단어를 포함하는 "규칙"을보십시오. 대부분의 경우이를 가르치는 사람들은 당신이 알아야 할 것을 숨기고 싶어하지만 그는 그렇지 않습니다. 가르치는 방법을 알고 있습니다. 마치 성인이성에 관한 질문에 아이에게 대답하는 것과 같습니다.
Emilio Garavaglia 2013 년

배치 기술 stroustrup.com/bs_faq2.html#placement-delete로 개체를 구성하는 경우에는 괜찮다고 생각합니다 (하지만 다소 낮은 수준이며 그러한 수준에서도 소프트웨어를 최적화 할 때만 사용됩니다)
bruziuz

답변:


95

operator new()" std::nothrow"오버로드를 사용하는 경우를 제외하고 의 오버로드 된 형식을 사용하여 개체가 생성 된 경우 소멸자를 수동으로 호출해야합니다 .

T* t0 = new(std::nothrow) T();
delete t0; // OK: std::nothrow overload

void* buffer = malloc(sizeof(T));
T* t1 = new(buffer) T();
t1->~T(); // required: delete t1 would be wrong
free(buffer);

명시 적으로 소멸자를 호출 위와 다소 낮은 수준에서 외부 관리 메모리는, 그러나, 이다 나쁜 디자인의 표시. 아마, 나쁜 디자인하지만 크게 잘못 단지는 (예, 할당 연산자의 복사 생성자 호출 다음에 명시 적으로 소멸자를 사용하여 실제로 입니다 나쁜 디자인과 가능성이 잘못 될).

C ++ 2011에는 명시 적 소멸자 호출을 사용하는 또 다른 이유가 있습니다. 일반화 된 공용체를 사용하는 경우 현재 개체를 명시 적으로 제거하고 표현 된 개체의 유형을 변경할 때 new 배치를 사용하여 새 개체를 만들어야합니다. 또한 공용체가 소멸 될 때 소멸이 필요한 경우 현재 객체의 소멸자를 명시 적으로 호출해야합니다.


26
"오버로드 된 형식을 사용하여"를 말하는 대신 operator new올바른 구문은 "사용 placement new"입니다.
Remy Lebeau 2013 년

5
@RemyLebeau : 글쎄, 나는 operator new(std::size_t, void*)(및 배열 변형)에 대해서만 이야기하는 것이 아니라 operator new().
Dietmar Kühl

작업이 계산되는 동안 개체를 변경하지 않고 작업을 수행하기 위해 개체를 복사하려면 어떻게해야합니까? temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
Jean-Luc Nacif Coelho

yes, using an explicit destructor followed by a copy constructor call in the assignment operator is a bad design and likely to be wrong. 왜 이말을하는거야? 소멸자가 사소하거나 사소한 경우에는 오버 헤드가 최소화되고 DRY 원칙의 사용이 증가한다고 생각합니다. 이동과 함께 이러한 경우에 operator=()사용하면 스왑을 사용하는 것보다 나을 수도 있습니다. YMMV.
Adrian

1
@Adrian : 소멸자를 호출하고 객체를 다시 생성하면 객체의 유형이 매우 쉽게 변경됩니다. 할당의 정적 유형으로 객체를 다시 생성하지만 동적 유형은 다를 수 있습니다. 이는 클래스에 virtual함수 가 있고 ( virtual함수는 다시 생성되지 않음 ) 실제로 문제가 되고 그렇지 않으면 객체가 부분적으로 [재구성]됩니다.
Dietmar Kühl 2018

105

모든 답변은 특정 사례를 설명하지만 일반적인 답변이 있습니다.

객체가 상주 하는 메모리 를 해제하지 않고 객체 (C ++ 의미에서)를 파괴해야 할 때마다 명시 적으로 dtor를 호출합니다 .

이것은 일반적으로 메모리 할당 / 할당 해제가 개체 생성 / 파괴와 독립적으로 관리되는 모든 상황에서 발생합니다. 이 경우 생성 은 존재하는 메모리 청크에 새로운 배치 를 통해 이루어지며 명시적인 dtor 호출을 통해 파괴가 발생합니다.

다음은 원시 예입니다.

{
  char buffer[sizeof(MyClass)];

  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }
  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }

}

또 다른 주목할만한 예는 기본값 std::allocator으로 사용될 때 std::vector요소에 구성되어 vector동안 push_back하지만 메모리가 너무 소자 제휴 미리 존재 청크에 할당된다. 따라서 vector::erase요소를 제거해야하지만 반드시 메모리 할당을 해제하는 것은 아닙니다 (특히 새로운 push_back이 곧 발생해야하는 경우 ...).

엄격한 OOP 의미에서 "나쁜 디자인"이고 (메모리가 아닌 개체를 관리해야합니다. 사실 개체에 메모리가 필요한 것은 "사고"입니다.) "저수준 프로그래밍"에서 "좋은 디자인"이거나 메모리가있는 경우 기본적으로 operator new구매 하는 "무료 상점"에서 가져 오지 않았습니다 .

코드 주변에서 무작위로 발생하면 나쁜 디자인이고, 해당 목적을 위해 특별히 설계된 클래스에 로컬로 발생하면 좋은 디자인입니다.


8
왜 이것이 허용되지 않는 대답인지 궁금합니다.
Francis Cugler

12

아니요, 두 번 호출되기 때문에 명시 적으로 호출해서는 안됩니다. 수동 호출의 경우 한 번, 객체가 선언 된 범위가 끝나는 다른 시간.

예 :

{
  Class c;
  c.~Class();
}

정말로 동일한 작업을 수행해야하는 경우 별도의 방법이 있어야합니다.

거기에있다 특정 상황 게재 위치에 동적으로 할당 된 객체의 소멸자를 호출 할 수있는는 new하지만 혹시 필요합니다 사운드 뭔가를하지 않습니다.


11

아니요, 상황에 따라 합법적이고 좋은 디자인 일 때도 있습니다 .

소멸자를 명시 적으로 호출해야하는 이유와시기를 이해하기 위해 "new"및 "delete"에서 어떤 일이 발생하는지 살펴 보겠습니다.

객체를 동적으로 생성하려면 T* t = new T;내부적으로 : 1. sizeof (T) 메모리가 할당됩니다. 2. 할당 된 메모리를 초기화하기 위해 T의 생성자가 호출됩니다. new 연산자는 할당과 초기화라는 두 가지 작업을 수행합니다.

delete t;후드 아래에 있는 물체를 파괴하려면 : 1. T의 소멸자가 호출됩니다. 2. 해당 개체에 할당 된 메모리가 해제됩니다. 삭제 연산자는 파괴와 할당 해제라는 두 가지 작업도 수행합니다.

하나는 생성자를 작성하여 초기화를 수행하고 소멸자를 작성하여 파괴합니다. 소멸자를 명시 적으로 호출하면 소멸 만 수행 되고 할당 해제 는 수행 되지 않습니다 .

따라서 명시 적으로 소멸자를 호출하는 합법적 인 사용은 "객체를 소멸시키기 만하고 싶지만 메모리 할당을 해제 할 수 없습니다 (아직)"일 수 있습니다.

이에 대한 일반적인 예는 동적으로 할당되어야하는 특정 개체의 풀에 대한 메모리를 미리 할당하는 것입니다.

새 개체를 만들 때 미리 할당 된 풀에서 메모리 청크를 가져와 "새로 배치"를 수행합니다. 개체 작업을 마친 후 소멸자를 명시 적으로 호출하여 정리 작업을 완료 할 수 있습니다. 그러나 연산자 삭제가 수행했듯이 실제로 메모리 할당을 해제하지는 않습니다. 대신 재사용을 위해 청크를 풀로 반환합니다.



6

할당과 초기화를 분리해야 할 때마다 소멸자를 수동으로 새롭고 명시 적으로 호출해야합니다. 오늘날에는 표준 컨테이너가 있으므로 거의 필요하지 않지만 새로운 종류의 컨테이너를 구현해야하는 경우 필요합니다.


3

필요한 경우가 있습니다.

내가 작업하는 코드에서 할당 자에서 명시 적 소멸자 호출을 사용하고 메모리 블록을 stl 컨테이너에 반환하기 위해 new 배치를 사용하는 간단한 할당자를 구현했습니다. 파괴에는 다음이 있습니다.

  void destroy (pointer p) {
    // destroy objects by calling their destructor
    p->~T();
  }

구성 중에 :

  void construct (pointer p, const T& value) {
    // initialize memory with placement new
    #undef new
    ::new((PVOID)p) T(value);
  }

또한 플랫폼 별 할당 및 할당 해제 메커니즘을 사용하여 allocate ()에서 할당이 수행되고 deallocate ()에서 메모리 할당 해제가 수행됩니다. 이 할당자는 doug lea malloc을 우회하고 예를 들어 Windows에서 LocalAlloc을 직접 사용하는 데 사용되었습니다.


1

이 작업을 수행해야하는 3 가지 경우를 발견했습니다.

  • memory-mapped-io 또는 공유 메모리에 의해 생성 된 메모리의 객체 할당 / 할당 해제
  • C ++를 사용하여 주어진 C 인터페이스를 구현할 때 (예, 안타깝게도 오늘날에도 여전히 발생합니다.
  • 할당 자 클래스를 구현할 때

1

수동으로 소멸자를 호출해야하는 상황을 본 적이 없습니다. Stroustrup이 나쁜 습관이라고 주장하는 것도 기억하는 것 같습니다.


1
당신이 올바른지. 그러나 나는 새로운 배치를 사용했습니다. 소멸자 이외의 메서드에서 정리 기능을 추가 할 수있었습니다. 소멸자는 삭제를 할 때 "자동으로"호출 될 수 있습니다. 수동으로 소멸을 원하지만 할당을 해제하지 않을 때 "onDestruct"를 작성할 수 있습니다. 때로는 삭제해야 할 때도 있고 할당을 해제하지 않고 파괴
만하고

그리고이 경우에도 소멸자 내에서 onDestruct ()를 호출 할 수 있으므로 소멸자를 수동으로 호출하는 경우는 여전히 보이지 않습니다.
Lieuwe

4
@JimBalter :의 창조자 C+
마크 K 코완

@MarkKCowan : C + 란 무엇입니까? 그것은 C ++해야한다
소멸자

1

이건 어때?
생성자에서 예외가 발생하면 소멸자가 호출되지 않으므로 예외 전에 생성자에서 생성 된 핸들을 파괴하려면 수동으로 호출해야합니다.

class MyClass {
  HANDLE h1,h2;
  public:
  MyClass() {
    // handles have to be created first
    h1=SomeAPIToCreateA();
    h2=SomeAPIToCreateB();        
    try {
      ...
      if(error) {
        throw MyException();
      }
    }
    catch(...) {
      this->~MyClass();
      throw;
    }
  }
  ~MyClass() {
    SomeAPIToDestroyA(h1);
    SomeAPIToDestroyB(h2);
  }
};

1
이것은 의문의 여지가있는 것 같습니다. 생성자가 실패 할 때 객체의 어떤 부분이 구성되었는지, 어떤 부분이 구성되지 않았는지 알지 못하거나 알 수 없습니다. 예를 들어 소멸자를 호출 할 하위 개체를 알 수 없습니다. 또는 할당 해제 할 생성자에 의해 할당 된 리소스입니다.
Violet Giraffe

@VioletGiraffe 하위 개체가 "new"가 아닌 스택에 생성 된 경우 자동으로 파괴됩니다. 그렇지 않으면 소멸자에서 삭제하기 전에 NULL인지 확인할 수 있습니다. 자원과 동일
CITBL

ctor여기에 작성한 방법 은 정확히 당신이 직접 제공 한 이유 때문에 잘못되었습니다. 리소스 할당이 실패하면 정리에 문제가 있습니다. 'ctor'는 this->~dtor(). 생성 된 객체에서 dtor호출되어야하며이 경우 객체는 아직 생성되지 않았습니다. 무슨 일이 있어도 는 정리를 처리해야합니다. 코드 내에서 무언가 발생하는 경우 자동 정리를 처리하는 것과 같은 유틸리티를 사용해야합니다 . 자동 정리를 지원하도록 클래스의 필드를 변경 하는 것도 좋은 생각 일 수 있습니다. ctorctorstd::unique_ptrHANDLE h1, h2
quetzalcoatl

ctor에 모양을해야한다고이 수단 : MyClass(){ cleanupGuard1<HANDLE> tmp_h1(&SomeAPIToDestroyA) = SomeAPIToCreateA(); cleanupGuard2<HANDLE> tmp_h2(&SomeAPIToDestroyB) = SomeAPIToCreateB(); if(error) { throw MyException(); } this->h1 = tmp_h1.release(); this->h2 = tmp_h2.release(); }그리고 그의 그것 . 위험한 수동 정리가 필요 없으며 모든 것이 안전 할 때까지 부분적으로 구성된 개체에 핸들을 보관하지 않아도됩니다. HANDLE h1,h2클래스를 cleanupGuard<HANDLE> h1;etc로 변경 하면 전혀 필요하지 않을 수도 있습니다 dtor.
quetzalcoatl

구현 cleanupGuard1cleanupGuard2관련 xxxToCreate반환을 수행하는 것과 관련이 xxxxToDestroy수행 하는 매개 변수 에 따라 다릅니다 . 그들이 간단하다면, std::unique_ptr<x,deleter()>두 경우 모두 (또는 비슷한 것)이 당신을 위해 트릭을 할 수 있다는 것이 종종 밝혀지기 때문에 아무것도 쓸 필요가 없을 수도 있습니다 .
quetzalcoatl

-2

소멸자를 수동으로 호출해야하는 또 다른 예를 찾았습니다. 여러 유형의 데이터 중 하나를 보유하는 변형 유사 클래스를 구현했다고 가정합니다.

struct Variant {
    union {
        std::string str;
        int num;
        bool b;
    };
    enum Type { Str, Int, Bool } type;
};

는 IF Variant인스턴스가 유지되었다 std::string, 지금 당신이 노동 조합에 다른 유형을 할당하고, 당신은 소멸합니다 std::string첫 번째. 컴파일러는이를 자동으로 수행하지 않습니다 .


-4

소멸자를 호출하는 것이 합리적이라고 생각하는 또 다른 상황이 있습니다.

개체를 초기 상태로 복원하기 위해 "Reset"유형의 메서드를 작성할 때 Destructor를 호출하여 재설정중인 이전 데이터를 삭제하는 것이 합리적입니다.

class Widget
{
private: 
    char* pDataText { NULL  }; 
    int   idNumber  { 0     };

public:
    void Setup() { pDataText = new char[100]; }
    ~Widget()    { delete pDataText;          }

    void Reset()
    {
        Widget blankWidget;
        this->~Widget();     // Manually delete the current object using the dtor
        *this = blankObject; // Copy a blank object to the this-object.
    }
};

1
cleanup()이 경우 소멸자에서 호출 할 특수 메서드를 선언하면 더 깔끔해 보이지 않습니까?
Violet Giraffe

두 가지 경우에만 호출되는 "특별한"메서드? 물론입니다 ... 완전히 맞습니다 (/ 풍자). 메서드는 일반화되어야하며 어디에서나 호출 할 수 있어야합니다. 객체를 삭제하려면 소멸자를 호출하는 데 아무런 문제가 없습니다.
abelenky

4
이 상황에서는 소멸자를 명시 적으로 호출하면 안됩니다. 어쨌든 할당 연산자를 구현해야합니다.
Rémi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.