IT

C ++ 소멸자는 언제 호출?

lottoking 2020. 8. 22. 09:59
반응형

C ++ 소멸자는 언제 호출?


기본 질문 : 프로그램이 C ++에서 클래스의 소멸자 메소드를 언제 호출합니까? 객체가 범위를 벗어나거나 대상이 될 때마다 호출된다고 들었습니다.delete

더 구체적인 질문 :

1) 주소가 포인터를 통해 생성되고 그 포인터가 나중에 삭제되거나 새 주소가 주어진 경우, 객체가 소멸 할 것입니다.

2) 질문 1에 대한 해결 조치로, 객체가 범위를 정의하는 것입니다 (객체가 주어진 {block}을 떠날 때와 관련이 없음). 즉, 연결 목록에서 소멸자가 언제 호출의 개체에서?

3) 소멸 손상 수동으로 호출하고 싶습니까?


1) 주소가 포인터를 통해 생성되고 그 포인터가 나중에 삭제되거나 새 주소가 주어진 경우, 객체가 소멸 할 것입니다.

포인터 유형에 따라 증가합니다. 예를 들어 스마트 포인터는 개체가 삭제 될 때 개체를 삭제하는 경우가 있습니다. 일반 포인터는 용어입니다. 포인터가 다른 개체를 가리 키도록 만들 때도 마찬가지입니다. 일부 스마트 포인터는 이전 개체를 파괴하거나 더 이상 참조가없는 경우 파괴합니다. 일반 포인터에는 없습니다. 그들은 단지 주소를 보유하고 특별히 그렇게 수행 할 수 있습니다.

2) 질문 1에 대한 해결 조치로, 객체가 범위를 정의하는 것입니다 (객체가 주어진 {block}을 떠날 때와 관련이 없음). 즉, 연결 목록에서 소멸자가 언제 호출의 개체에서?

그것은 연결 목록의 구현에 달려 있습니다. 일반적인 컬렉션은 소멸시 포함 된 개체를 소멸합니다.

따라서 포인터 포인터 목록은 일반적으로 포인터가 가리키는 개체가 아닌 포인터를 파괴합니다. (정확할 수 있습니다. 다른 포인터에 의한 참조 일 수 있습니다.) 그러나 포인터를 포함 할 수 있습니다.

스마트 포인터의 연결 목록은 포인터가 삭제 될 때 개체를 자동으로 삭제하거나 더 이상 참조가없는 경우 삭제합니다. 원하는 것을 수행하는 조각을 선택하는 것은 모두 당신에게 달려 있습니다.

3) 소멸 손상 수동으로 호출하고 싶습니까?

확실합니다. 한 가지 예는 객체를 동일한 유형의 다른 객체로 바꾸고 싶지만 다시 할당하기 위해 메모리를 해제하지 않습니다. 이전 개체를 제자리에서 파괴하고 제자리에 새 개체를 구성 할 수 있습니다. (그러나 일반적으로 이것은 나쁜 생각입니다.)

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}

다른 사람들은 이미 다른 문제를 해결했거나 한 가지 사항 만 보겠습니다. 개체를 수동으로 삭제 하시겠습니까?

대답은‘예’입니다. @DavidSchwartz가 한 가지 예를 제공했지만 상당히 드문 경우입니다. 많은 C ++ 프로그래머가 항상 사용하는 후드 아래에있는 예제를 제공합니다. std::vector(그리고 std::deque많이 사용되지는 않지만).

대부분의 사람들이 알다시피, std::vector은 (는) 현재 할당이 보유 할 수있는 것보다 더 많은 항목을 추가 할 때 더 큰 메모리 블록을 할당합니다. 작업을 수행 그러나이하면 현재 벡터에있는을 구석으로보다 더 많은 object- 를 보유 할 수있는 메모리 블록 이 있습니다.

관리하기 위해이를 내부적으로하는 vector일은 object- 를 통해 원시 메모리를 할당 하는 Allocator을 구석으로입니다 (달리 지정하지 않는 한를 사용함을 의미 함 ::operator new). 그런 다음 (예를 들어) push_back사용 하여 vector내부적으로 벡터는 placement new사용하여 메모리 공간의 (이전) 사용되지 않은 부분에 항목을 만듭니다.

이제 erase벡터의 항목이 있으면 괜찮다고 ? 그냥 사용할 수 없습니다 delete. 전체 메모리 블록을 해제합니다. 다른 개체를 파괴하지 않고 제어하는 ​​메모리 블록을 해제하지 않고 해당 메모리의 한 개체를 파괴해야합니다 (예 erase: 벡터에서 push_back5 개 항목 을 즉시 5 개 이상 추가 벡터가 재 할당 되지 않음을 보장 합니다. 당신이 그렇게 할 때 기억.

이를 위해 벡터는 사용하지 않고 소멸 해 명시 적으로 호출하여 메모리의 객체를 직접 파괴합니다 delete.

만약 다른 사람이 같은 방식 vector(또는 std::deque실제 방식 과 같은 일부 변형)을 사용하여 연속 저장소를 만들어서 작성 하고 거의 같은 기술을 사용하고 싶을 것입니다.

예를 들어 원형 링 버퍼에 대한 코드를 작성하는 방법을 고려해 보겠습니다.

#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC

template <class T>
class circular_buffer {
    T *data;
    unsigned read_pos;
    unsigned write_pos;
    unsigned in_use;
    const unsigned capacity;
public:
    circular_buffer(unsigned size) :
        data((T *)operator new(size * sizeof(T))),
        read_pos(0),
        write_pos(0),
        in_use(0),
        capacity(size)
    {}

    void push(T const &t) {
        // ensure there's room in buffer:
        if (in_use == capacity) 
            pop();

        // construct copy of object in-place into buffer
        new(&data[write_pos++]) T(t);
        // keep pointer in bounds.
        write_pos %= capacity;
        ++in_use;
    }

    // return oldest object in queue:
    T front() {
        return data[read_pos];
    }

    // remove oldest object from queue:
    void pop() { 
        // destroy the object:
        data[read_pos++].~T();

        // keep pointer in bounds.
        read_pos %= capacity;
        --in_use;
    }

    // release the buffer:
~circular_buffer() { operator delete(data); }
};

#endif

컨테이너와 달리 표준 이는 operator newoperator delete직접 사용 합니다. 실제 사용을 말하는 할당 자 클래스를 사용하고 싶겠지 만 현재 기여하는 것보다주의를 산만하게하는 것이 더 많은 (IMO, 어쨌든).


  1. 를 사용하여 생성을 만들 때 new를 호출해야 delete합니다. 사용하여 object-를를 만들면 make_shared결과 shared_ptr는 카운트를 유지 delete하고 사용 횟수가 0 될 때이 호출 합니다.
  2. 범위를 벗어나는 것은 블록을 떠나는 것을 의미합니다. 이것은 객체가 할당 되지 않았다고 가정하여 소멸자가 호출 될 때 입니다 new(즉, 스택 객체).
  3. 소멸자를 명시 적으로 호출해야하는 유일한 경우는 배치new 와 함께 객체를 할당 할 때 입니다.

1) 개체는 '포인터를 통해'생성되지 않습니다. '새로 만들기'개체에 할당 된 포인터가 있습니다. 이것이 의미하는 바라고 가정하면 포인터에서 'delete'를 호출하면 포인터가 역 참조하는 객체를 실제로 삭제 (소멸자를 호출)합니다. 포인터를 다른 개체에 할당하면 메모리 누수가 발생합니다. C ++의 어떤 것도 쓰레기를 수집하지 않습니다.

2) 두 가지 질문이 있습니다. 변수는 선언 된 스택 프레임이 스택에서 튀어 나오면 범위를 벗어납니다. 보통 이것은 당신이 블록을 떠날 때입니다. 힙의 개체는 스택에 대한 포인터가있을 수 있지만 범위를 벗어나지 않습니다. 특별히 연결된 목록에있는 개체의 소멸자가 호출된다는 보장은 없습니다.

3) 그렇지 않습니다. 달리 제안 할 수있는 Deep Magic이있을 수 있지만 일반적으로 '새'키워드를 '삭제'키워드와 일치시키고 소멸자에 모든 것을 넣어 제대로 정리되도록합니다. 이렇게하지 않으면 해당 클래스를 사용하는 모든 사람에게 해당 개체의 리소스를 수동으로 정리하는 방법에 대한 특정 지침으로 소멸자에 주석을 달아야합니다.


질문 3에 대한 자세한 답변을 제공하려면 예, 특히 dasblinkenlight가 관찰 한 것처럼 소멸자를 명시 적으로 호출 할 수있는 경우가 있습니다.

이에 대한 구체적인 예를 들면 다음과 같습니다.

#include <iostream>
#include <new>

struct Foo
{
    Foo(int i_) : i(i_) {}
    int i;
};

int main()
{
    // Allocate a chunk of memory large enough to hold 5 Foo objects.
    int n = 5;
    char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));

    // Use placement new to construct Foo instances at the right places in the chunk.
    for(int i=0; i<n; ++i)
    {
        new (chunk + i*sizeof(Foo)) Foo(i);
    }

    // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
    for(int i=0; i<n; ++i)
    {
        Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
        std::cout << foo->i << '\n';
        foo->~Foo();
    }

    // Deallocate the original chunk of memory.
    ::operator delete(chunk);

    return 0;
}

이런 종류의 목적은 객체 생성에서 메모리 할당을 분리하는 것입니다.


  1. 포인터 -일반 포인터는 RAII를 지원하지 않습니다. 명시 적이 없으면 delete쓰레기가 있습니다. 다행히 C ++에는 이를 처리하는 자동 포인터 가 있습니다!

  2. 범위 -변수가 프로그램에서 보이지 않게 될 때를 생각해보십시오 . 일반적으로 이것은 {block}당신이 지적한 바와 같이 의 끝에 있습니다.

  3. 수동 파괴 -절대로 시도하지 마십시오. 스코프와 RAII가 당신을 위해 마법을 맡기십시오.


"new"를 사용할 때, 즉 포인터에 주소를 첨부하거나 힙의 공간을 요구할 때마다이를 "삭제"해야합니다.
1. 예, 무언가를 삭제하면 소멸자가 호출됩니다.
2. Linked List의 소멸자가 호출되면 해당 객체의 소멸자가 호출됩니다. 그러나 포인터 인 경우 수동으로 삭제해야합니다. 3. "신규"가 공간을 주장하는 경우.


예, 소멸자 (일명 dtor)는 개체가 스택에있는 경우 범위를 벗어나거나 delete개체에 대한 포인터를 호출 할 때 호출 됩니다.

  1. 포인터 delete가을 통해 삭제 되면 dtor가 호출됩니다. delete먼저 호출하지 않고 포인터를 다시 할당하면 개체가 메모리 어딘가에 여전히 존재하기 때문에 메모리 누수가 발생합니다. 후자의 경우 dtor가 호출되지 않습니다.

  2. 좋은 연결 목록 구현은 목록이 소멸 될 때 목록에있는 모든 객체의 dtor를 호출합니다 (일부 메서드를 호출하여 삭제하거나 범위를 벗어 났기 때문). 이것은 구현에 따라 다릅니다.

  3. 의심 스럽지만 이상한 상황이 있어도 놀라지 않을 것입니다.


객체가 포인터를 통하지 않고 생성 된 경우 (예 : A a1 = A ();), 객체가 소멸 될 때 소멸자가 호출되며, 항상 해당 객체가있는 함수가 완료 될 때 호출됩니다.

void func()
{
...
A a1 = A();
...
}//finish


소멸자는 코드가 "finish"줄에 실행될 때 호출됩니다.

포인터 (예 : A * a2 = new A ();)를 통해 객체가 생성 된 경우 포인터가 삭제 될 때 소멸자가 호출됩니다 (delete a2;). 사용자가 명시 적으로 포인트를 삭제하지 않거나 새 주소를 삭제하기 전에 메모리 누수가 발생합니다. 그것은 버그입니다.

연결 목록에서 std :: list <>를 사용하면 std :: list <>가이 모든 작업을 완료했기 때문에 desctructor 또는 메모리 누수에 대해 신경 쓸 필요가 없습니다. 우리 자신이 작성한 연결 목록에서 desctructor를 작성하고 포인터를 명시 적으로 삭제해야하며 그렇지 않으면 메모리 누수가 발생합니다.

소멸자를 수동으로 호출하는 경우는 거의 없습니다. 시스템에 제공하는 기능입니다.

불쌍한 영어에 대해 죄송합니다!


객체의 생성자는 해당 객체에 메모리가 할당 된 직후에 호출되는 반면 소멸자는 해당 객체의 메모리 할당을 해제하기 직전에 호출됩니다.

참고 URL : https://stackoverflow.com/questions/10081429/when-is-ac-destructor-called

반응형