Programing

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

lottogame 2020. 10. 23. 07:29
반응형

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


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


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 배치를 사용하여 새 개체를 만들어야합니다. 또한 공용체가 소멸 될 때 소멸이 필요한 경우 현재 객체의 소멸자를 명시 적으로 호출해야합니다.


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

객체가 상주 하는 메모리 를 해제하지 않고 객체 (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구매 하는 "무료 상점"에서 가져 오지 않았습니다 .

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


아니요, 상황에 따라 때로는 합법적이고 좋은 디자인입니다.

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

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

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

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

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

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

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


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

예 :

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

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

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


FAQ에 인용 된대로 배치 new를 사용할 때 소멸자를 명시 적으로 호출해야합니다 .

이것은 소멸자를 명시 적으로 호출 한 유일한 경우입니다.

나는 이것이 거의 필요하지 않지만 동의합니다.


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


필요한 경우가 있습니다.

내가 작업하는 코드에서 할당 자에서 명시 적 소멸자 호출을 사용하고 메모리 블록을 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);
  }

there is also allocation being done in allocate() and memory deallocation in deallocate(), using platform specific alloc and dealloc mechanisms. This allocator was used to bypass doug lea malloc and use directly for example LocalAlloc on windows.


I found 3 occasions where I needed to do this:

  • allocating/deallocating objects in memory created by memory-mapped-io or shared memory
  • when implementing a given C interface using C++ (yes this still happens today unfortunately (because I don't have enough clout to change it))
  • when implementing allocator classes

What about this?
Destructor is not called if an exception is thrown from the constructor, so I have to call it manually to destroy handles that have been created in the constructor before the exception.

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);
  }
};

I have never come across a situation where one needs to call a destructor manually. I seem to remember even Stroustrup claims it is bad practice.


Found another example where you would have to call destructor(s) manually. Suppose you have implemented a variant-like class that holds one of several types of data:

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

If the Variant instance was holding a std::string, and now you're assigning a different type to the union, you must destruct the std::string first. The compiler will not do that automatically.


I have another situation where I think it is perfectly reasonable to call the destructor.

When writing a "Reset" type of method to restore an object to its initial state, it is perfectly reasonable to call the Destructor to delete the old data that is being reset.

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.
    }
};

참고URL : https://stackoverflow.com/questions/14187006/is-calling-destructor-manually-always-a-sign-of-bad-design

반응형