[c++] shared_ptr <void>가 합법적이지만 unique_ptr <void>는 형식이 잘못된 이유는 무엇입니까?

질문은 제목에 정말 적합합니다.이 차이에 대한 기술적 이유가 무엇인지 알고 싶은데 그 이유도 궁금합니다.

std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;



답변

이 때문에 인 std::shared_ptr반면, 구현 형 삭제 std::unique_ptr하지 않습니다.


std::shared_ptr유형 삭제를 구현 하기 때문에 또 다른 흥미로운 속성 인 viz 도 지원합니다 . 클래스 템플릿에 대한 템플릿 유형 인수로 삭제 자의 유형이 필요 하지 않습니다 . 그들의 선언을보십시오 :

template<class T,class Deleter = std::default_delete<T> > 
class unique_ptr;

이는 보유 Deleter하면서, 입력 파라미터로서

template<class T> 
class shared_ptr;

없습니다.

이제 질문은 왜 shared_ptr유형 삭제를 구현합니까? 음, 그렇게합니다. 참조 계산을 지원해야하고이를 지원하기 위해 힙에서 메모리를 할당해야 하고 어쨌든 메모리 할당해야하므로 한 단계 더 나아가 유형 삭제를 구현합니다. 이는 힙이 필요합니다. 할당도. 그래서 기본적으로 그것은 단지 기회 주의적입니다!

유형 삭제로 인해는 std::shared_ptr두 가지를 지원할 수 있습니다.

  • 이 같은 유형의 객체를 저장할 수 있습니다 void*, 하지만 여전히 제대로 파괴에 개체를 삭제할 수 있습니다 올바르게에 의해 자신의 소멸자를 호출 .
  • deleter의 유형은 클래스 템플릿에 유형 인수로 전달되지 않으므로 type-safety를 손상시키지 않고 약간의 자유를 의미합니다 .

좋구나. 그것이 어떻게 std::shared_ptr작동 하는지에 관한 것 입니다.

이제 질문은 std::unique_ptr객체 저장할 수 void*있습니까? 음, 대답은 그렇습니다 . 적절한 삭제자를 인수로 전달했다면 그렇습니다 . 다음은 그러한 데모 중 하나입니다.

int main()
{
    auto deleter = [](void const * data ) {
        int const * p = static_cast<int const*>(data);
        std::cout << *p << " located at " << p <<  " is being deleted";
        delete p;
    };

    std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter);

} //p will be deleted here, both p ;-)

출력 ( 온라인 데모 ) :

959 located at 0x18aec20 is being deleted

댓글에 매우 흥미로운 질문을했습니다.

제 경우에는 유형 삭제 삭제자가 필요하지만 가능해 보입니다 (일부 힙 할당 비용으로). 기본적으로 이것은 실제로 세 번째 유형의 스마트 포인터에 대한 틈새 지점이 있음을 의미합니까 : 유형 삭제가있는 독점 소유권 스마트 포인터입니다.

이는에 @ 스티브 Jessop는 다음과 같은 솔루션을 제안,

나는 실제로 이것을 시도한 적이 없지만 아마도 삭제 자 std::function유형으로 적절한 것을 사용하여 달성 할 수 unique_ptr있습니까? 실제로 작동한다고 가정하면 독점 소유권과 유형 삭제 삭제가 완료됩니다.

이 제안에 따라 이것을 구현했습니다 ( std::function필요하지 않은 것처럼 사용 하지는 않지만).

using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;

template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
    return unique_void_ptr(ptr, [](void const * data) {
         T const * p = static_cast<T const*>(data);
         std::cout << "{" << *p << "} located at [" << p <<  "] is being deleted.\n";
         delete p;
    });
}

int main()
{
    auto p1 = unique_void(new int(959));
    auto p2 = unique_void(new double(595.5));
    auto p3 = unique_void(new std::string("Hello World"));
}  

출력 ( 온라인 데모 ) :

{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.

도움이 되었기를 바랍니다.


답변

이론적 근거 중 하나는 a의 많은 사용 사례 중 하나에 있습니다. shared_ptr즉, 수명 지표 또는 보초로 사용됩니다.

이것은 원래의 부스트 문서에서 언급되었습니다.

auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv)
{
    auto closure_target = { closure, std::weak_ptr<void>(pv) };
    ...
    // store the target somewhere, and later....
}

void call_closure(closure_target target)
{
    // test whether target of the closure still exists
    auto lock = target.sentinel.lock();
    if (lock) {
        // if so, call the closure
        target.closure();
    }
}

closure_target다음과 같은 것이 어디에 있습니까?

struct closure_target {
    std::function<void()> closure;
    std::weak_ptr<void> sentinel;
};

호출자는 다음과 같은 콜백을 등록합니다.

struct active_object : std::enable_shared_from_this<active_object>
{
    void start() {
      event_emitter_.register_callback([this] { this->on_callback(); }, 
                                       shared_from_this());
    }

    void on_callback()
    {
        // this is only ever called if we still exist 
    }
};

shared_ptr<X>는 항상로 변환 할 수 있기 때문에 shared_ptr<void>event_emitter는 이제 다시 호출하는 객체의 유형을 인식하지 못할 수 있습니다.

이 배열은 구독자에게 교차 사례를 처리 할 의무 (active_object가 사라지는 동안 조치를 기다리는 동안 대기열에 콜백이 있다면 어떻게 될까요?)를 이벤트 이미 터에 해제하고 구독 취소를 동기화 할 필요가 없음을 의미합니다. weak_ptr<void>::lock동기화 된 작업입니다.


답변