나는 생각했다 : 그들은 소멸자를 수동으로 호출한다면-당신은 뭔가 잘못하고 있다고 말한다. 그러나 항상 그렇습니까? 반례가 있습니까? 수동으로 호출해야하는 상황이나 피하는 것이 어렵거나 불가능하거나 비현실적인 상황입니까?
new
를 호출 하여 이전 객체 대신 새 객체를 초기화합니다. 일반적으로 좋은 생각은 아니지만 들어 본 적이 없습니다.
나는 생각했다 : 그들은 소멸자를 수동으로 호출한다면-당신은 뭔가 잘못하고 있다고 말한다. 그러나 항상 그렇습니까? 반례가 있습니까? 수동으로 호출해야하는 상황이나 피하는 것이 어렵거나 불가능하거나 비현실적인 상황입니까?
new
를 호출 하여 이전 객체 대신 새 객체를 초기화합니다. 일반적으로 좋은 생각은 아니지만 들어 본 적이 없습니다.
답변:
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 배치를 사용하여 새 개체를 만들어야합니다. 또한 공용체가 소멸 될 때 소멸이 필요한 경우 현재 객체의 소멸자를 명시 적으로 호출해야합니다.
operator new
올바른 구문은 "사용 placement new
"입니다.
operator new(std::size_t, void*)
(및 배열 변형)에 대해서만 이야기하는 것이 아니라 operator new()
.
temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
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.
virtual
함수 가 있고 ( virtual
함수는 다시 생성되지 않음 ) 실제로 문제가 되고 그렇지 않으면 객체가 부분적으로 [재구성]됩니다.
모든 답변은 특정 사례를 설명하지만 일반적인 답변이 있습니다.
객체가 상주 하는 메모리 를 해제하지 않고 객체 (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. 해당 개체에 할당 된 메모리가 해제됩니다. 삭제 연산자는 파괴와 할당 해제라는 두 가지 작업도 수행합니다.
하나는 생성자를 작성하여 초기화를 수행하고 소멸자를 작성하여 파괴합니다. 소멸자를 명시 적으로 호출하면 소멸 만 수행 되고 할당 해제 는 수행 되지 않습니다 .
따라서 명시 적으로 소멸자를 호출하는 합법적 인 사용은 "객체를 소멸시키기 만하고 싶지만 메모리 할당을 해제 할 수 없습니다 (아직)"일 수 있습니다.
이에 대한 일반적인 예는 동적으로 할당되어야하는 특정 개체의 풀에 대한 메모리를 미리 할당하는 것입니다.
새 개체를 만들 때 미리 할당 된 풀에서 메모리 청크를 가져와 "새로 배치"를 수행합니다. 개체 작업을 마친 후 소멸자를 명시 적으로 호출하여 정리 작업을 완료 할 수 있습니다. 그러나 연산자 삭제가 수행했듯이 실제로 메모리 할당을 해제하지는 않습니다. 대신 재사용을 위해 청크를 풀로 반환합니다.
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);
}
또한 플랫폼 별 할당 및 할당 해제 메커니즘을 사용하여 allocate ()에서 할당이 수행되고 deallocate ()에서 메모리 할당 해제가 수행됩니다. 이 할당자는 doug lea malloc을 우회하고 예를 들어 Windows에서 LocalAlloc을 직접 사용하는 데 사용되었습니다.
이 작업을 수행해야하는 3 가지 경우를 발견했습니다.
수동으로 소멸자를 호출해야하는 상황을 본 적이 없습니다. Stroustrup이 나쁜 습관이라고 주장하는 것도 기억하는 것 같습니다.
C+
☺
이건 어때?
생성자에서 예외가 발생하면 소멸자가 호출되지 않으므로 예외 전에 생성자에서 생성 된 핸들을 파괴하려면 수동으로 호출해야합니다.
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);
}
};
ctor
여기에 작성한 방법 은 정확히 당신이 직접 제공 한 이유 때문에 잘못되었습니다. 리소스 할당이 실패하면 정리에 문제가 있습니다. 'ctor'는 this->~dtor()
. 생성 된 객체에서 dtor
호출되어야하며이 경우 객체는 아직 생성되지 않았습니다. 무슨 일이 있어도 는 정리를 처리해야합니다. 코드 내에서 무언가 발생하는 경우 자동 정리를 처리하는 것과 같은 유틸리티를 사용해야합니다 . 자동 정리를 지원하도록 클래스의 필드를 변경 하는 것도 좋은 생각 일 수 있습니다. ctor
ctor
std::unique_ptr
HANDLE h1, h2
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
.
cleanupGuard1
및 cleanupGuard2
관련 xxxToCreate
반환을 수행하는 것과 관련이 xxxxToDestroy
수행 하는 매개 변수 에 따라 다릅니다 . 그들이 간단하다면, std::unique_ptr<x,deleter()>
두 경우 모두 (또는 비슷한 것)이 당신을 위해 트릭을 할 수 있다는 것이 종종 밝혀지기 때문에 아무것도 쓸 필요가 없을 수도 있습니다 .
소멸자를 수동으로 호출해야하는 또 다른 예를 찾았습니다. 여러 유형의 데이터 중 하나를 보유하는 변형 유사 클래스를 구현했다고 가정합니다.
struct Variant {
union {
std::string str;
int num;
bool b;
};
enum Type { Str, Int, Bool } type;
};
는 IF Variant
인스턴스가 유지되었다 std::string
, 지금 당신이 노동 조합에 다른 유형을 할당하고, 당신은 소멸합니다 std::string
첫 번째. 컴파일러는이를 자동으로 수행하지 않습니다 .
소멸자를 호출하는 것이 합리적이라고 생각하는 또 다른 상황이 있습니다.
개체를 초기 상태로 복원하기 위해 "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.
}
};
cleanup()
이 경우 와 소멸자에서 호출 할 특수 메서드를 선언하면 더 깔끔해 보이지 않습니까?