Adottato da qui .
La maggior parte dei modelli nella libreria standard C ++ richiede che vengano istanziati con tipi completi. Tuttavia shared_ptre unique_ptrsono eccezioni parziali . Alcuni, ma non tutti i loro membri, possono essere istanziati con tipi incompleti. La motivazione di ciò è supportare idiomi come il pimpl usando i puntatori intelligenti e senza rischiare comportamenti indefiniti.
Un comportamento indefinito può verificarsi quando si dispone di un tipo incompleto e lo si chiama delete:
class A;
A* a = ...;
delete a;
Quanto sopra è un codice legale. Si compilerà. Il compilatore può o meno emettere un avviso per il codice sopra come quello sopra. Quando viene eseguito, probabilmente accadranno cose brutte. Se sei molto fortunato, il tuo programma andrà in crash. Tuttavia, un risultato più probabile è che il tuo programma perderà silenziosamente memoria come ~A()non verrà chiamato.
L'uso auto_ptr<A>nell'esempio sopra non aiuta. Ottieni ancora lo stesso comportamento indefinito come se avessi usato un puntatore non elaborato.
Tuttavia, l'uso di lezioni incomplete in determinati luoghi è molto utile! Questo è dove shared_ptre unique_ptraiuto. L'uso di uno di questi suggerimenti intelligenti ti permetterà di cavartela con un tipo incompleto, tranne dove è necessario avere un tipo completo. E, soprattutto, quando è necessario disporre di un tipo completo, si ottiene un errore di compilazione se si tenta di utilizzare il puntatore intelligente con un tipo incompleto in quel punto.
Niente più comportamenti indefiniti:
Se il tuo codice viene compilato, hai utilizzato un tipo completo ovunque sia necessario.
class A
{
class impl;
std::unique_ptr<impl> ptr_; // ok!
public:
A();
~A();
// ...
};
shared_ptre unique_ptrrichiede un tipo completo in luoghi diversi. Le ragioni sono oscure, hanno a che fare con un deleter dinamico rispetto a un deleter statico. Le ragioni precise non sono importanti. In effetti, nella maggior parte dei codici non è molto importante sapere esattamente dove è richiesto un tipo completo. Basta codificare e, se sbagli, il compilatore te lo dirà.
Tuttavia, nel caso in cui ti sia utile, ecco una tabella che documenta diversi membri shared_ptre unique_ptrrispetto ai requisiti di completezza. Se il membro richiede un tipo completo, la voce ha una "C", altrimenti la voce della tabella viene riempita con "I".
Complete type requirements for unique_ptr and shared_ptr
unique_ptr shared_ptr
+------------------------+---------------+---------------+
| P() | I | I |
| default constructor | | |
+------------------------+---------------+---------------+
| P(const P&) | N/A | I |
| copy constructor | | |
+------------------------+---------------+---------------+
| P(P&&) | I | I |
| move constructor | | |
+------------------------+---------------+---------------+
| ~P() | C | I |
| destructor | | |
+------------------------+---------------+---------------+
| P(A*) | I | C |
+------------------------+---------------+---------------+
| operator=(const P&) | N/A | I |
| copy assignment | | |
+------------------------+---------------+---------------+
| operator=(P&&) | C | I |
| move assignment | | |
+------------------------+---------------+---------------+
| reset() | C | I |
+------------------------+---------------+---------------+
| reset(A*) | C | C |
+------------------------+---------------+---------------+
Qualsiasi operazione che richiede conversioni di puntatori richiede tipi completi per entrambi unique_ptre shared_ptr.
Il unique_ptr<A>{A*}costruttore può cavarsela con un incompleto Asolo se al compilatore non è richiesto di impostare una chiamata ~unique_ptr<A>(). Ad esempio se metti l' unique_ptrheap, puoi farcela con un incompleto A. Maggiori dettagli su questo punto sono disponibili nella risposta di BarryTheHatchet qui .
shared_ptr/unique_ptr" di Howard Hinnant La tabella alla fine dovrebbe rispondere alla tua domanda.