큰 목록을 파괴하면 스택이 오버플로됩니까? 스택을 날려 버립니까? 이 공정은

다음과 같은 단일 연결 목록 구현을 고려하십시오.

struct node {
    std::unique_ptr<node> next;
    ComplicatedDestructorClass data;
}

이제 std::unique_ptr<node> head인스턴스를 사용하지 않고 범위를 벗어난 소멸자를 호출 한다고 가정 해보십시오 .

이것이 충분히 큰 목록에 대해 내 스택을 날려 버립니까? 이 공정은 컴파일러 (인라인 꽤 복잡 최적화를 할 것이라고 가정하는 것입니다 unique_ptr‘로 소멸자를 node내가 할 경우 이후 다음 (더 힘들어 얻을 수있는, 다음의’꼬리 재귀를 사용) data소멸자 당황 것 next‘의 하드를 만들기 컴파일러가 잠재적 재정렬 및 ​​테일 콜 기회를 알 수 있도록) :

struct node {
    std::shared_ptr<node> next;
    ComplicatedDestructorClass data;
}

data어떻게 든 그것의 포인터가 있다면 node꼬리 재귀가 불가능할 수도 있습니다 (물론 캡슐화의 위반을 피하기 위해 노력해야 함).

일반적으로 어떻게하면이 목록을 어떻게 파괴해야합니까? 공유 포인터에는 release! 가 없으므로 목록을 탐색하고 “현재”노드를 삭제할 수 없습니다 . 유일한 방법은 사용자 정의 삭제 도구를 사용하는 것입니다.



답변

예, 컴파일러가 node의 소멸자 소멸자에 꼬리 호출 최적화를 적용하지 않는 한 결국 스택을 날려 버릴 것 shared_ptr입니다. 후자는 표준 라이브러리 구현에 크게 의존합니다. 예를 들어, Microsoft의 STL은 shared_ptr해당 대상의 참조 카운트를 먼저 줄인 다음 (객체를 파괴 할 수 있음) 제어 블록의 참조 카운트 (약한 참조 카운트)를 줄 이므로 절대로 그렇게하지 않습니다 . 따라서 내부 소멸자는 꼬리 호출이 아닙니다. 또한 가상 호출 이므로 최적화 될 가능성이 훨씬 적습니다.

일반적인 목록은 하나의 노드가 다음 노드를 소유하지 않고 모든 노드를 소유하는 하나의 컨테이너를 가지며 루프를 사용하여 소멸자의 모든 것을 삭제하여 문제를 해결합니다.


답변

늦은 대답이지만 아무도 그것을 제공하지 않았기 때문에 … 같은 문제가 발생하여 사용자 정의 소멸자를 사용하여 해결했습니다.

virtual ~node () throw () {
    while (next) {
        next = std::move(next->next);
    }
}

실제로 목록 이있는 경우 , 즉 모든 노드 앞에 하나의 노드가 있고 최대 하나의 추종자가 있고 귀하 list가 첫 번째에 대한 포인터 node인 경우 위의 작업이 필요합니다.

퍼지 구조 (예 : 비순환 그래프)가있는 경우 다음을 사용할 수 있습니다.

virtual ~node () throw () {
    while (next && next.use_count() < 2) {
        next = std::move(next->next);
    }
}

아이디어는 당신이 할 때 :

next = std::move(next->next);

이전 공유 포인터 next는 삭제 use_count되었으므로 (지금 이기 때문에 0) 다음을 가리 킵니다. 재귀 대신 반복적으로 수행하므로 스택 오버플로를 피한다는 점을 제외하면 기본 소멸자와 정확히 동일합니다.


답변

솔직히 말하면 나는 C ++ 컴파일러의 스마트 포인터 할당 해제 알고리즘에 익숙하지 않지만 이것을 수행하는 간단한 비 재귀 알고리즘을 상상할 수 있습니다. 이걸 고려하세요:

  • 할당 해제를 기다리는 스마트 포인터 대기열이 있습니다.
  • 첫 번째 포인터를 가져 와서 할당을 해제하고 큐가 비워 질 때까지 이것을 반복하는 함수가 있습니다.
  • 스마트 포인터가 할당 해제가 필요한 경우 대기열로 푸시되고 위 함수가 호출됩니다.

따라서 스택이 오버플로 될 가능성이 없으며 재귀 알고리즘을 최적화하는 것이 훨씬 간단합니다.

이것이 “거의 제로 비용 스마트 포인터”철학에 맞는지 확실하지 않습니다.

나는 당신이 묘사 한 것이 스택 오버플로를 유발하지는 않을 것이라고 생각하지만, 당신은 나를 잘못 증명하는 영리한 실험을 시도 할 수 있습니다.

최신 정보

글쎄, 이것은 내가 이전에 쓴 내용이 잘못되었음을 증명합니다.

#include <iostream>
#include <memory>

using namespace std;

class Node;

Node *last;
long i;

class Node
{
public:
   unique_ptr<Node> next;
   ~Node()
   {
     last->next.reset(new Node);
     last = last->next.get();
     cout << i++ << endl;
   }
};

void ignite()
{
    Node n;
    n.next.reset(new Node);
    last = n.next.get();
}

int main()
{
    i = 0;
    ignite();
    return 0;
}

이 프로그램은 노드 체인을 영원히 구축하고 해체합니다. 스택 오버플로가 발생합니다.