2016-09-21 1 views
3

공유 포인터는 매우 똑똑합니다. 그들은 올바르게 삭제하기 위해 처음에 만들어 낸 유형을 기억합니다. 예를 들면 가져가 :shared_ptr를 <T>의 shared_ptr에 업 캐스팅 할 수 있습니까? <void>가 정의되지 않은 동작을 일으킬 수 있습니까?

struct A { virtual void test() = 0; }; 
struct B : A { void test() override {} }; 

void someFunc() { 
    std::shared_ptr<A> ptr1; 

    ptr1 = std::make_shared<B>(); 

    // Here at the end of the scope, B is deleted correctly 
} 

그러나, 무효 포인터에 문제가있을 것 같다 무효 포인터의 내리 뜬가 유효하려면, 하나는 원래부터 upcasted 된 유형으로 다운 캐스트해야합니다. 예를 들어

: std::shared_ptr

void* myB = new B; 

// Okay, well defined 
doStuff(static_cast<B*>(myB)); 

// uh oh, not good! 
// For the same instance of a child object, a pointer to the base and 
// a pointer to the child can be differrent. 
doStuff(static_cast<A*>(myB)); 

, 당신은 std::make_shared를 사용할 때 Deleter가이 기능과 유사합니다 [](B* ptr){ delete ptr; }. 포인터 (첫 번째 예제에서)는 A에 대한 포인터에 B 인스턴스를 보유하고 올바르게 삭제하기 때문에 어떤 식 으로든이를 내 버려야합니다.

제 질문은 : 다음 코드 단편은 정의되지 않은 동작입니까?

void someFunc() { 
    { 
     std::shared_ptr<void> ptr = std::make_shared<B>(); 

     // Deleting the pointer seems okay to me, 
     // the shared_ptr knows that a B was originally allocated with a B and 
     // will send the void pointer to the deleter that's delete a B. 
    } 

    std::shared_ptr<void> vptr; 

    { 
     std::shared_ptr<A> ptr = std::make_shared<B>(); 

     // ptr is pointing to the base, which can be 
     // different address than the pointer to the child. 

     // assigning the pointer to the base to the void pointer. 
     // according to my current knowledge of void pointers, 
     // any future use of the pointer must cast it to a A* or end up in UB. 
     vptr = ptr; 
    } 

    // is the pointer deleted correctly or it tries to 
    // cast the void pointer to a B pointer without downcasting to a A* first? 
    // Or is it well defined and std::shared_ptr uses some dark magic unknown to me? 
} 

답변

5

코드는 정확합니다.

std::shared_ptr은 실제 포인터와 실제 deleter가 생성자에있는대로 내부적으로 저장하므로 다운 캐스트가 유효하더라도 삭제자가 유효하면 삭제자가 옳습니다.

실제로 shared_ptr에는 개체에 대한 포인터가 저장되지 않지만 실제 개체 인 참조 카운터와 삭제자를 보유하는 중간 구조체에 대한 포인터가 들어 있습니다. shared_ptr을 전송하면 중간 구조체가 변경되지 않습니다. vptrptr은 유형이 다르긴하지만 참조 카운터 (물론 개체 및 삭제 자)를 공유하기 때문에 변경할 수 없습니다.

중간 구조체는 make_shared 최적화의 이유입니다. 중간 구조체와 개체 자체를 동일한 메모리 블록에 할당하고 추가 할당을 피합니다.

, 나는 때문에 문제의 (GCC 6.2.1와) 충돌 일반 포인터와 프로그램을 작성했습니다 포인터가 얼마나 스마트 설명하기 :

#include <memory> 
#include <iostream> 

struct A 
{ 
    int a; 
    A() :a(1) {} 
    ~A() 
    { 
     std::cout << "~A " << a << std::endl; 
    } 
}; 

struct X 
{ 
    int x; 
    X() :x(3) {} 
    ~X() 
    { 
     std::cout << "~X " << x << std::endl; 
    } 
}; 

struct B : X, A 
{ 
    int b; 
    B() : b(2) {} 
    ~B() 
    { 
     std::cout << "~B " << b << std::endl; 
    } 
}; 

int main() 
{ 
    A* a = new B; 
    void * v = a; 
    delete (B*)v; //crash! 

    return 0;  
} 

실제로 잘못된 정수 값을 출력하는 UB를 증명합니다.

~B 0 
~A 2 
~X 1 
*** Error in `./a.out': free(): invalid pointer: 0x0000000001629c24 *** 

그러나 스마트 포인터를 가진 버전은 잘 작동합니다 :

int main() 
{ 
    std::shared_ptr<void> vptr; 

    { 
     std::shared_ptr<A> ptr = std::make_shared<B>(); 
     vptr = ptr; 
    } 
    return 0; 
} 

그것은으로 예상 인쇄합니다 :

~B 2 
~A 1 
~X 3 
+0

나는 항상 shared_ptr의 디자인 완성도를 전달하는 정답을 upvote 할 것이다. +1 :) –

+0

@RichardHodges 나는 오버 헤드의 관점에서 무료이므로'std :: unique_ptr'을 선호한다. 대답은, 좋은 직업 @rodrigo, 그것은 거의 모든 것을 다룹니다. –

+1

'shared_ptr' *는 shared_ptr :: get()에서 반환 된 포인터를 가지고 있습니다. 그러나 이것은 Deleter에게 전달되는 포인터가 아닙니다. –

4

shared_ptr 항상 Deleter가 원래 포인터를 전달하지 않는 한 vptr.get()을 통해 얻습니다. 이것은이 경우를 작동시키는 것뿐만 아니라 생성자 오버로드 shared_ptr<T>::shared_ptr(const shared_ptr<T>&, element_type*)에 구체화 된 멤버 하위 객체 및 소유 객체에 대한 포인터를 갖는 데 필요합니다.

이렇게하면 안전합니다.

관련 문제