Waarom gebruikt unique_ptr twee sjabloonparameters als shared_ptr er maar één nodig heeft?

Zowel unique_ptrals shared_ptraccepteren een aangepast verwijderprogramma om het object dat ze bezitten aan te roepen. Maar in het geval van unique_ptrwordt de verwijderaar doorgegeven als een sjabloonparameter van de klasse, terwijl het type aangepaste verwijderaar van shared_ptris te specificeren als een sjabloonparameter van de constructor.

template <class T, class D = default_delete<T>> 
class unique_ptr
{
    unique_ptr(T*, D&); //simplified
    ...
};

en

template<class T>
class shared_ptr
{
    template<typename D>
    shared_ptr(T*, D); //simplified
    ...
};

Ik begrijp niet waarom zo’n verschil. Wat vereist dat?


Antwoord 1, autoriteit 100%

Als je de deleter opgeeft als sjabloonargument (zoals in unique_ptr), maakt het deel uit van het type en hoef je niets extra’s op te slaan in de objecten van dit type.
Als deleter wordt doorgegeven als argument van de constructor (zoals in shared_ptr), moet je het in het object opslaan. Dit zijn de kosten van extra flexibiliteit, aangezien u verschillende verwijderaars kunt gebruiken voor de objecten van hetzelfde type.

Ik denk dat dit de reden is: unique_ptrwordt verondersteld een zeer lichtgewicht object te zijn zonder overhead. Het opslaan van verwijderaars bij elke unique_ptrzou hun grootte kunnen verdubbelen. Daarom zouden mensen in plaats daarvan goede oude onbewerkte wijzers gebruiken, wat verkeerd zou zijn.

Aan de andere kant is shared_ptrniet zo licht, omdat het referentieaantal moet opslaan, dus het opslaan van een aangepaste deleter lijkt ook een goede afweging.


Antwoord 2, autoriteit 5%

Gedeelde pointers van verschillende typenkunnen het eigendom van het hetzelfde objectdelen. Zie overbelasting (8)van std::shared_ptr::shared_ptr. Unieke pointers hebben zo’n mechanisme niet nodig, omdat ze niet delen.

template< class Y > 
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;

Als je de deleter niet had gewist, zou je zo’n shared_ptr<T, Y_Deleter>niet kunnen gebruiken als een shared_ptr<T>, wat het eigenlijk nutteloos zou maken.

Waarom zou je zo’n overbelasting willen?

Overweeg

struct Member {};
struct Container { Member member };

Als je de Containerin leven wilt houden, terwijl je het Membergebruikt, dan kan dat

std::shared_ptr<Container> pContainer = /* something */
std::shared_ptr<Member> pMember(pContainer, &pContainer->member);

en alleen pMembervast te houden (misschien zet het in een std::vector<std::shared_ptr<Member>>)

Of anders overbelasting (9) gebruiken

template< class Y > 
shared_ptr( const shared_ptr<Y>& r ) noexcept; 
  // Only exists if Y* is implicitly convertible to T*

U kunt polymorf delen hebben

struct Base {};
struct Derived : Base {};
void operate_on_base(std::shared_ptr<Base>);
std::shared_ptr<Derived> pDerived = /* something*/
operate_on_base(pDerived);

Other episodes