c++ pointer - Perché unique_ptr richiede due parametri del modello quando shared_ptr ne prende uno solo?




11 reset (3)

Sia unique_ptr che shared_ptr accettano un distruttore personalizzato per chiamare l'oggetto di loro proprietà. Ma nel caso di unique_ptr , il distruttore viene passato come parametro del modello della classe , mentre il tipo di distruttore personalizzato di shared_ptr deve essere specificato come parametro del modello del costruttore .

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

e

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

Non riesco a capire perché tale differenza. Cosa lo richiede?


Answers

Puntatori condivisi di tipi diversi possono condividere la proprietà dello stesso oggetto . Vedi overload (8) di std::shared_ptr::shared_ptr . I puntatori unici non hanno bisogno di un tale meccanismo, in quanto non condividono .

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

Se non si digita il cancellatore del testo, non si sarebbe in grado di utilizzare tale shared_ptr<T, Y_Deleter> come shared_ptr<T> , il che lo renderebbe sostanzialmente inutile.

Perché vuoi un tale sovraccarico?

Prendere in considerazione

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

Se vuoi mantenere vivo il Container , mentre usi il Member , puoi farlo

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

e devo solo tenere premuto pMember (magari inserirlo in un file std::vector<std::shared_ptr<Member>> )

In alternativa, usando il sovraccarico (9)

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

Puoi avere una condivisione polimorfica

struct Base {};
struct Derived : Base {};

void operate_on_base(std::shared_ptr<Base>);

std::shared_ptr<Derived> pDerived = /* something*/
operate_on_base(pDerived);

Se fornisci il deleter come argomento modello (come in unique_ptr ) fa parte del tipo e non è necessario memorizzare nulla di aggiuntivo negli oggetti di questo tipo. Se il deleter viene passato come argomento del costruttore (come in shared_ptr ) è necessario memorizzarlo nell'oggetto. Questo è il costo di una maggiore flessibilità, dal momento che è possibile utilizzare diversi deletatori per gli oggetti dello stesso tipo.

Immagino che questo sia il motivo: unique_ptr dovrebbe essere un oggetto molto leggero con zero overhead. Memorizzare i unique_ptr con ciascun unique_ptr potrebbe raddoppiare le loro dimensioni. Per questo motivo la gente userebbe invece dei buoni vecchi puntatori grezzi, il che sarebbe sbagliato.

D'altra parte, shared_ptr non è così leggero, dal momento che ha bisogno di memorizzare il conteggio dei riferimenti, quindi anche la memorizzazione di un deleter personalizzato sembra un buon compromesso.


Un segfault è al di fuori del sistema delle eccezioni del C ++. Se si denota un puntatore nullo, non si ottiene alcun tipo di eccezione generata (beh, almeno se si rispetta la clausola Require: vedere sotto per i dettagli).

Per operator-> , in genere viene implementato semplicemente come return m_ptr; (o return get(); per unique_ptr ). Come puoi vedere, l'operatore non può lanciare: restituisce semplicemente il puntatore. Nessun dereferenziamento, niente di niente. La lingua ha alcune regole speciali per p->identifier :

§13.5.6 [over.ref] p1

Un'espressione x->m viene interpretata come (x.operator->())->m per un oggetto di classe x di tipo T se T::operator->() esiste e se l'operatore è selezionato come la funzione di corrispondenza migliore dal meccanismo di risoluzione del sovraccarico (13.3).

Quanto sopra si applica in modo ricorsivo e alla fine deve fornire un puntatore, per il quale viene utilizzato l' operator-> incorporato- operator-> . Ciò consente agli utenti di puntatori e iteratori intelligenti di fare semplicemente smart->fun() senza preoccuparsi di nulla.

Una nota per il Require: parti della specifica: questi indicano le precondizioni. Se non li incontri, stai invocando UB.

Perché allora, uno di questi è specificato come no-outs e l'altro no?

Ad essere onesti, non ne sono sicuro. Sembrerebbe che il dereferenziamento di un puntatore debba sempre essere non noexcept , tuttavia, unique_ptr ti permette di cambiare completamente il tipo di puntatore interno (attraverso il deleter). Ora, come utente, puoi definire semantica completamente diversa per operator* sul tuo tipo di pointer . Forse calcola le cose al volo? Tutte quelle cose divertenti, che possono essere gettate.

Guardando std :: shared_ptr abbiamo questo:

Questo è facile da spiegare - shared_ptr non supporta la suddetta personalizzazione del tipo di puntatore, il che significa che la semantica integrata si applica sempre - e *p dove p è T* semplicemente non gira.







c++ c++11 std shared-ptr unique-ptr