2015-01-02 12 views
19

Pomyślałem, że jest bardzo ciekawy, gdy odkryłem, że standard definiuje std::unique_ptr i std::shared_ptr na dwa całkowicie różne sposoby dotyczące Deletera, który może posiadać wskaźnik. Oto oświadczenie cppreference::unique_ptr i cppreference::shared_ptr:Typ Deletera w unique_ptr vs. shared_ptr

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

template< class T > class shared_ptr; 

Jak widać unique_ptr "oszczędza" rodzaj The Deleter-obiektu jako szablonu argumentu. To również może być postrzegane w sposób Deleter pobranym z wskaźnikiem później:

// unique_ptr has a member function to retrieve the Deleter 
template< 
    class T, 
    class Deleter = std::default_delete<T> 
> 
Deleter& unique_ptr<T, Deleter>::get_deleter(); 

// For shared_ptr this is not a member function 
template<class Deleter, class T> 
Deleter* get_deleter(const std::shared_ptr<T>& p); 

Może ktoś wyjaśnić racjonalne za tę różnicę? Zdecydowanie faworyzuję koncepcję unique_ptr, dlaczego nie jest to również stosowane w odniesieniu do shared_ptr? Ponadto, dlaczego w tym ostatnim przypadku get_deleter byłaby funkcją nieczłonkowską?

+2

Ktoś będzie musiał wykopać oryginalną propozycję, ale moje wykształcone domysły: brak deletera jako argumentu szablonu sprawia, że ​​'shared_ptr' jest łatwiejszy w użyciu, ale musisz zapłacić koszty wymazywania typu. Uczynienie 'get_deleter' członkiem spowoduje napisanie ogólnego kodu z' shared_ptr 'bardziej żmudnym - musisz napisać' sp.template get_deleter () 'zamiast' get_deleter (sp) '. Z tego powodu 'std :: get' nie jest członkiem. –

+3

Poszerzenie nieco o to, co @ T.C. powiedział, jednym z celów projektu 'unique_ptr' jest to, że powinien on mieć (bardzo blisko) zero narzutów. Wymazywanie typu deletera jest wygodne, ale wprowadza dodatkowy czas działania z wymazywania, więc jest mniej odpowiedni dla 'unique_ptr' niż dla' shared_ptr' – wakjah

+0

Należy również zauważyć, że z powodu tej różnicy, 'shared_ptr p = make_shared ()' robi to, nawet jeśli 'Base' nie ma wirtualnego destruktora. [dowód] (http://coliru.stacked-crooked.com/a/f3a50f90e00d4e58). –

Odpowiedz

18

Tutaj można znaleźć oryginalną propozycję dla inteligentnych wskaźników: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1450.html

To odpowiedzi na swoje pytanie dość precyzyjnie:

Ponieważ Deleter nie jest częścią typu, zmieniając strategię alokacji nie łamie Zgodność ze źródłami lub binarnymi i nie wymaga rekompilacji klienta.

Jest to także użyteczne dlatego, że daje klientom std::shared_ptr niektórych bardziej elastyczny, np shared_ptr przykłady z różnymi deleters mogą być przechowywane w tym samym pojemniku.

Ponadto, ponieważ implementacje shared_ptr wymagają bloku pamięci współdzielonej (do przechowywania licznika referencyjnego), a także dlatego, że w przedziale czasowym musi być trochę narzut w porównaniu do surowych wskaźników, dodanie usuniętego typu deletera nie jest wielkim problemem tutaj.

unique_ptr, z drugiej strony, są przeznaczone do tego, aby nie mieć żadnych kosztów ogólnych, a każda instancja musi osadzić swój deleter, więc uczynienie go częścią tego typu jest rzeczą naturalną.

+0

Jak nazywa się deleterem typu btw? – WorldSEnder

+0

@WorldSEnder: To zależy od implementacji, ale zwykłym sposobem jest enkapsulacja konkretnego deltera do klasy szablonowej, która implementuje (dziedziczy) interfejs do usuwania T *. Tak więc, gdy licznik odwołań osiąga zero, implementacja wywołuje metodę wirtualną, wywołanie to jest wywoływane w klasie osadzania, która z kolei wywołuje konkretny deleter. Jest to ten sam mechanizm, co w przypadku funkcji std ::. – Horstling

+1

Zauważ, że "surowa" implementacja niższego poziomu 'std :: function' i prawdopodobnie' shared_ptr' będzie w praktyce szybsza niż wersja bazująca na vtable, ale korzysta z nich niewiele bibliotek std (zobacz 'najszybszych możliwych delegatów' przez google). Implementacja vtable jest najłatwiejsza do zrozumienia, ponieważ wygląda normalnie w C++. – Yakk

Powiązane problemy