`enable_shared_from_this`의 유용성은 무엇입니까?


349

나는 가로 질러 enable_shared_from_thisBoost.Asio 예제를 읽는 동안 나는 여전히 올바르게 사용하는 방법에 대한 분실하고있는 문서를 읽고. 누군가이 수업을 사용할 때의 설명과 설명을 해 줄 수 있습니까?

답변:


362

당신이 가진 모든 경우에 유효한 shared_ptr인스턴스 를 얻을 수 있습니다 . 그게 없으면, 당신은을 얻는 방법이없는 것 에 이미 구성원으로 일했다하지 않는 한을. enable_shared_from_this에 대한 부스트 문서 의이 예제는 다음과 같습니다.thisthisshared_ptrthis

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

멤버 인스턴스가 f()없더라도이 메소드 는 valid를 리턴 shared_ptr합니다. 간단히 이렇게 할 수는 없습니다.

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}

이것에 의해 반환 된 공유 포인터는 "적절한"참조와는 다른 참조 카운트를 가지게되며, 그중 하나는 객체가 삭제 될 때 매달려있는 참조를 잃어 버릴 수 있습니다.

enable_shared_from_thisC ++ 11 표준의 일부가되었습니다. 부스트뿐만 아니라 거기에서도 얻을 수 있습니다.


202
+1. 요점은 shared_ptr <Y> (this)를 반환하는 "명백한"기술이 분리되었다는 것입니다. 이는 별도의 참조 카운트를 가진 여러 개의 구별 된 shared_ptr 객체를 생성하기 때문입니다. 이러한 이유로 동일한 raw 포인터에서 둘 이상의 shared_ptr 작성해서는 안됩니다 .
j_random_hacker

3
에 주목해야한다 (11) 이후 ++ C , 완벽하게 유효한 용도에 std::shared_ptrA의 생성자를 원시 포인터 경우 는 상속 std::enable_shared_from_this. Boost의 의미가이를 지원하도록 업데이트 되었는지 여부는 알 수 없습니다.
Matthew

6
@MatthewHolder 이에 대한 견적이 있습니까? cppreference.com에서 " std::shared_ptr다른 사람이 이미 관리하고있는 객체를 구성 std::shared_ptr하면 내부에 저장된 약한 참조를 참조하지 않으므로 정의되지 않은 동작이 발생합니다." 라고 읽습니다 . ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
Thorbjørn Lindeijer 님이

5
왜 안 해 shared_ptr<Y> q = p?
Dan M.

2
@ ThorbjørnLindeijer, 맞습니다 .C ++ 17 이상입니다. 일부 구현은 C ++ 16 시맨틱이 릴리스되기 전에 수행되었습니다. C ++ 11에서 C ++ 14까지의 올바른 처리 방법을 사용해야 std::make_shared<T>합니다.
Matthew

198

약한 포인터에 대한 Dr Dobbs 기사 에서이 예제를 이해하기 쉽다고 생각합니다 (출처 : http://drdobbs.com/cpp/184402026 ).

... 이와 같은 코드는 올바르게 작동하지 않습니다.

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

둘 중 어느 것도 shared_ptr 개체 다른 개체에 대해 알지 못하므로 개체가 모두 파괴되면 리소스를 해제하려고합니다. 일반적으로 문제가 발생합니다.

마찬가지로 멤버 함수에 호출되는 shared_ptr객체를 소유 한 객체가 필요한 경우 즉시 객체를 만들 수 없습니다.

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

이 코드는 이전 예제와 같은 문제가 있지만 더 미묘한 형식입니다. 그것이 구축 될 때, shared_ptr 객체 sp1는 새로 할당 된 리소스를 소유합니다. 멤버 함수 내부의 코드 S::dangerous는 해당 shared_ptr객체 에 대해 알지 못 하므로 shared_ptr반환 되는 객체는와 다릅니다 sp1. 새 shared_ptr객체를 복사 sp2해도 도움이되지 않습니다. 때 sp2범위를 벗어나, 그것은 리소스를 분리 한 경우 것이다 sp1범위를 벗어나, 다시 리소스를 해제합니다.

이 문제를 피하는 방법은 클래스 템플릿을 사용하는 것 enable_shared_from_this입니다. 템플리트는 하나의 템플리트 유형 인수를 사용하는데, 이는 관리 자원을 정의하는 클래스의 이름입니다. 해당 클래스는 템플릿에서 공개적으로 파생되어야합니다. 이처럼 :

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}

이 작업을 수행 할 때 호출 shared_from_this하는 shared_ptr객체 는 객체 가 소유해야 합니다. 작동하지 않습니다.

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}

15
감사합니다. 현재 승인 된 답변보다 문제가 더 잘 해결되고 있음을 보여줍니다.
goertzenator

2
+1 : 좋은 답변입니다. 옆으로, 대신 shared_ptr<S> sp1(new S);사용하는 것이 좋습니다. shared_ptr<S> sp1 = make_shared<S>();예를 들어 stackoverflow.com/questions/18301511/…
Arun

4
마지막 줄을 읽어야한다고 확신합니다. shared_ptr<S> sp2 = p->not_dangerous();여기의 함정 은 처음 호출하기 전에 일반적인 방법으로 shared_ptr을 만들어야shared_from_this() 한다는 것입니다 ! 이것은 정말로 잘못되기 쉽습니다! C ++ 17 전에이다 UB가 전화 shared_from_this(): 정확히 하나의 shared_ptr의 정상적인 방법으로 생성 된 이전 auto sptr = std::make_shared<S>();또는 shared_ptr<S> sptr(new S());. 고맙게도 C ++ 17부터 그렇게하면 던져 질 것입니다.
AnorZaken


2
@AnorZaken 좋은 지적입니다. 수정을 위해 수정 요청을 제출 한 경우 유용했을 것입니다. 방금 했어요 또 다른 유용한 것은 포스터가 주관적이고 상황에 맞는 메소드 이름을 선택하지 않았을 것입니다!
underscore_d

30

여기에 볼트와 너트 관점에서 내 설명이 있습니다 (최고의 답변은 나와 '클릭'하지 않았습니다). * 이것은 Visual Studio 2012와 함께 제공되는 shared_ptr 및 enable_shared_from_this의 소스를 조사한 결과입니다. 아마도 다른 컴파일러는 enable_shared_from_this를 다르게 구현합니다 ... *

enable_shared_from_this<T>weak_ptr<T>인스턴스에 T대한 ' 하나의 참 참조 카운트 '를 보유 하는 프라이빗 인스턴스를 추가합니다 T.

따라서 shared_ptr<T>새로운 T *에 처음으로를 생성하면 해당 T *의 내부 weak_ptr이 1의 참조 횟수로 초기화됩니다. 새로운 shared_ptr기본적으로이로 돌아 weak_ptr갑니다.

T그리고, 그 방법으로 호출 할 수 shared_from_this의 인스턴스를 획득하기 위해 shared_ptr<T>같은 내부에 저장된 레퍼런스 카운트에 뒤를 . 이렇게하면 항상 서로에 대해 알지 못하는 T*여러 shared_ptr인스턴스가 아닌 참조 횟수가 저장 되는 한 곳이 있으며 , 각 shared_ptr참조 횟수는 참조 횟수를 계산 T하고 참조 할 때 삭제하는 담당자 라고 생각합니다. -카운트가 0에 도달합니다.


1
이것은 정확하며 정말 중요한 부분은 So, when you first create... 그것이 요구 사항 이기 때문에 (약점 _ptr은 객체 포인터를 shared_ptr ctor에 전달할 때까지 초기화되지 않습니다!)이 요구 사항은 당신이 잘못되면 상황이 심각하게 잘못 될 수 있기 때문입니다 조심하지 마십시오. 호출하기 전에 shared_ptr을 작성하지 않으면 shared_from_thisUB를 얻습니다. 마찬가지로 하나 이상의 shared_ptr을 작성하면 UB도 얻습니다. 어떻게 든 shared_ptr을 정확히 한 번만 작성해야합니다 .
AnorZaken

2
다시 말해 enable_shared_from_this 점은 얻을 수있을 것입니다 때문에 처음부터 부서지기 shared_ptr<T>A로부터 T*하지만, 현실에서 당신은 포인터를 얻을 때 T* t그것은 이미 공유 여부 것에 대해 무엇을 생각하는 일반적으로 안전하지 않고, 잘못된 추측은 UB입니다.
AnorZaken

" 내부 weak_ptr은 1의 참조 횟수로 초기화됩니다. "약한 ptr-T는 소유하지 않은 스마트 ptr입니다. 약한 ptr은 소유하는 ptr을 다른 소유 ptr의 "복사"로 만들기에 충분한 정보를 소유하는 소유 한 ref입니다. 약한 ptr에는 참조 횟수가 없습니다. 모든 소유 심판과 마찬가지로 심판 카운트에 액세스 할 수 있습니다.
curiousguy

3

boost :: intrusive_ptr을 사용하면이 문제가 발생하지 않습니다. 이것은 종종이 문제를 해결하는 더 편리한 방법입니다.


예, 그러나 enable_shared_from_this특별히 허용하는 API로 작업 할 수 있습니다 shared_ptr<>. 내 의견으로는, 그러한 API는 일반적으로 잘못하고 있습니다 (스택에서 더 높은 것이 메모리를 소유하는 것이 더 낫기 때문에) 그러나 그러한 API로 작업 해야하는 경우 이것은 좋은 옵션입니다.
cdunn2001

2
가능한 한 표준 내에서 유지하는 것이 좋습니다.
Sergei

3

c ++ 11 이상에서 정확히 동일 합니다. 원시 포인터를 제공 this하므로 공유 포인터 로 반환 this할 수 있습니다.

다시 말해, 이처럼 코드를 바꿀 수 있습니다

class Node {
public:
    Node* getParent const() {
        if (m_parent) {
            return m_parent;
        } else {
            return this;
        }
    }

private:

    Node * m_parent = nullptr;
};           

이것으로 :

class Node : std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getParent const() {
        std::shared_ptr<Node> parent = m_parent.lock();
        if (parent) {
            return parent;
        } else {
            return shared_from_this();
        }
    }

private:

    std::weak_ptr<Node> m_parent;
};           

이러한 객체가 항상에 의해 관리되는 경우에만 작동합니다 shared_ptr. 인터페이스가 올바른지 확인하고 싶을 수도 있습니다.
curiousguy

1
당신은 절대적으로 @curiousguy입니다. 이것은 말할 것도없이 진행됩니다. 또한 공용 API를 정의 할 때 가독성을 높이기 위해 모든 shared_ptr을 typedefing하는 것이 좋습니다. 예를 들어, 대신 std::shared_ptr<Node> getParent const()일반적으로 NodePtr getParent const()대신 노출합니다 . 내부 원시 포인터 (가장 좋은 예 : C 라이브러리 처리)에 액세스 해야하는 std::shared_ptr<T>::get경우이 원시 포인터 접근자가 잘못된 이유로 너무 여러 번 사용했기 때문에 언급하기가 싫습니다.
mchiasson

-3

다른 방법은에 weak_ptr<Y> m_stub멤버 를 추가하는 것 class Y입니다. 그런 다음 다음을 작성하십시오.

shared_ptr<Y> Y::f()
{
    return m_stub.lock();
}

파생하는 수업을 변경할 수없는 경우에 유용합니다 (예 : 다른 사람의 도서관 확장). 멤버를 초기화하는 것을 잊지 마십시오 (예 : by m_stub = shared_ptr<Y>(this))는 생성자 중에도 유효합니다.

상속 계층 구조에 이와 같은 스텁이 더 있으면 오브젝트가 손상되지 않습니다.

편집 : 사용자 nobar가 올바르게 지적한 것처럼 코드는 할당이 완료되고 임시 변수가 파괴되면 Y 객체를 파괴합니다. 따라서 내 대답이 잘못되었습니다.


4
여기서 당신의 의도가 shared_ptr<>그것의 포인트를 삭제하지 않는 것을 생산하는 것이라면, 이것은 과잉입니다. 단항 함수 객체가 return shared_ptr<Y>(this, no_op_deleter);어디에 있고 아무것도하지 않는 no_op_deleter것을 간단히 말할 수 있습니다 Y*.
존 즈 빙크

2
이것이 작동하는 해결책은 아닙니다. m_stub = shared_ptr<Y>(this)이것으로부터 임시 shared_ptr을 구성하고 즉시 파괴합니다. 이 문장이 끝나면 this삭제되고 모든 후속 참조가 매달려 있습니다.
nobar

2
저자는이 답변이 잘못되었다는 것을 인정하여 아마도 그것을 삭제할 수있을 것입니다. 그러나 그는 4.5 년 만에 마지막으로 로그인 했으므로 그렇게하지 않을 것입니다-더 높은 힘을 가진 사람 이이 빨간 청어를 제거 할 수 있습니까?
Tom Goodfellow

의 구현을 살펴보면 (ctor에 의해 채워진) 자체를 enable_shared_from_this유지하고 호출 할 때로 weak_ptr반환됩니다 . 다시 말해서, 당신은 이미 제공 한 것을 복제하고 있습니다. shared_ptrshared_from_thisenable_shared_from_this
mchiasson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.