2015-05-04 13 views
8

Non ho mai usato alcun tipo di puntatore intelligente, ma continuo a leggerli quasi ovunque quando l'argomento è puntato. Capisco che ci sono situazioni in cui i puntatori intelligenti sono molto più piacevoli con i puntatori grezzi, perché in qualche modo gestiscono la proprietà del puntatore. Tuttavia, non lo so ancora, dov'è il confine tra "Non ho bisogno di puntatori intelligenti per questo" e "questo è un caso per i puntatori intelligenti".Perché dovrei voler utilizzare un puntatore intelligente in questa situazione?

Diciamo, ho la seguente situazione:

class A { 
public: 
    double get1(){return 1;} 
    double get2(){return 2;} 
}; 
class SomeUtilityClass { 
public: 
    SomeUtilityClass(A* a) : a(a) {} 
    double getResult(){return a->get1() + a->get2();} 
    void setA(A* a){a = a;} 
private: 
    A* a; 
}; 
int main(int argc, char** argv) { 
    A a; 
    SomeUtilityClass u(&a); 
    std::cout << u.getResult() << std::endl; 
    A a2; 
    u.setA(&a2); 
    std::cout << u.getResult() << std::endl; 
    return 0; 
} 

Si tratta naturalmente di un esempio semplificato. Quello che intendo è che lo SomeUtilityClass non dovrebbe "possedere" un'istanza di A (perché è solo una classe di utilità), quindi mantiene solo un puntatore.

Per quanto riguarda il puntatore, l'unica cosa che mi rendo conto di quello che poteva andare storto è:

  • SomeUtilityClass può essere istanziata con un puntatore nullo
  • L'oggetto puntato può essere cancellato/uscire campo di applicazione, senza la SomeUtilityClass accorgersene

Come potrebbe un puntatore aiuto intelligente per evitare questo problema? Quali altri benefici otterrei utilizzando un puntatore intelligente in questo caso?

PS: So che ci sono diverse domande sui puntatori intelligenti (ad esempio this one). Tuttavia, sarei grato se potessi parlarmi dell'impatto su questo particolare esempio.

+7

I puntatori grezzi catturano perfettamente il concetto di riferimento non proprietario a dati con una durata scoped. Non dovresti usare i puntatori intelligenti qui. – Mankarse

+5

Se stai usando 'new' in the wild, è un suggerimento abbastanza forte che potresti aver bisogno di un puntatore intelligente. Se stai usando 'new []', è un suggerimento abbastanza forte che potresti aver bisogno di 'std :: vector' o di un altro contenitore. – JBL

+1

Se un 'nullptr' non ha senso, potresti prendere in considerazione un riferimento o' std :: reference_wrapper'. –

risposta

1

Ai fini di questa risposta che sto ridefinendo Seta come:

void setA(A* new_a){a = new_a;} 

Considerate:

// Using your SomeUtilityClass 

int main() { 
    A a; 
    SomeUtilityClass u(&a); 
    // We define a new scope, just because: 
    { 
    A b; 
    u.setA(&b); 
    } 
    std::cout << u.getResult() << '\n'; 
    return 0; 
} 

Dopo che il campo di applicazione è terminata, SomeUtilityClass ha un puntatore penzoloni e getResult() invoca non definita Comportamento. Si noti che questo non può essere risolto con un riferimento: si otterrebbe comunque uno penzolante.

Consideriamo ora il versione utilizzando un puntatore intelligente:

class SomeUtilityClass { 
public: 
    SomeUtilityClass(std::shared_ptr<A>& a) : a{a} {} 
    double getResult(){return a->get1() + a->get2();} 
    void setA(std::shared_ptr<A>& new_a){a = new_a;} 
private: 
    std::shared_ptr<A> a; 
}; 

int main() { 
    std::shared_ptr<A> a{new A}; 
    SomeUtilityClass u{a}; 
    // We define a new scope, just because: 
    { 
    std::shared_ptr<A> b{new A}; 
    u.setA(b); 
    } 
    std::cout << u.getResult() << '\n'; 
    return 0; 
} 

Poiché hai proprietà condivisa, non c'è modo per ottenere un puntatore penzoloni. La memoria puntata da b verrà cancellata come al solito, ma solo dopo che u viene distrutto (o il suo puntatore viene modificato).

IMHO, nella maggior parte dei casi si dovrebbero usare puntatori intelligenti (anche se inizialmente non sembra avere molto senso). Rende la manutenzione molto più semplice. Utilizza i puntatori non elaborati solo nel codice specifico che ne ha effettivamente bisogno e incapsula/isola questo codice il più possibile.

1

Se SomeUtilityClass non possiede la variabile membro a, un puntatore intelligente non ha senso.

Si potrebbe considerare un membro di riferimento, che rimuoverebbe i problemi di un puntatore nullo.

2

Dipende dal modo in cui il parametro viene creato e memorizzato. Se non si possiede la memoria e potrebbe essere allocata staticamente o dinamicamente, un puntatore raw è una soluzione perfettamente ragionevole, specialmente se è necessario supportare lo scambio dei dati come nell'esempio. Un'altra opzione sarebbe quella di utilizzare std::reference_wrapper, che eliminerebbe il tuo problema nullptr mantenendo la stessa semantica.

Se siete in possesso di un puntatore ad una risorsa condivisa (cioè conservati in un std::shared_ptr da qualche parte) e vuole essere in grado di verificare se è stato cancellato o meno, si potrebbe tenere un std::weak_ptr.

1

Il modo predefinito per esprimere un puntatore non proprietario in C++ è weak_ptr.Per utilizzare weak_ptr è necessario utilizzare shared_ptr per la proprietà, così nel tuo esempio si usa

shared_ptr<A> owner(...) 

invece di

A a 

Poi, come il membro puntatore privato della vostra SomeUtilityClass si utilizza puntatore debole:

weak_ptr<A> w; 

e inizializzarlo con shared_ptr:

tuttavia, non è possibile utilizzare direttamente weak_ptr, dal momento che lo shared_ptr potrebbe uscire dall'ambito e il puntatore debole non può più puntare a nulla. Prima dell'uso è necessario bloccarlo:

shared_ptr<A> locked = w.lock(); 

Il puntatore locked sarà vuota se il puntatore possedere non riesce più un oggetto, dal momento che per esempio è andato fuori portata. Se non è vuoto, puoi usarlo e poi uscirà dall'ambito rilasciando automaticamente il blocco dell'oggetto.

Entrambi shared_ptr e weak_ptr sono disponibili nella libreria standard in C++ 11 e in Boost per i compilatori precedenti.

+1

'weak_ptr' non è il modo predefinito per esprimere i non possedere.È usato per rompere i cicli quando viene usato 'shared_ptr'. Il modo predefinito per esprimere non possedere è un puntatore raw. –

+0

@SebastianRedl Hai dichiarato la tua opinione personale come generalmente accettata. Tuttavia, [cppreference] (http://en.cppreference.com/w/cpp/memory/weak_ptr) dice _std :: weak_ptr modella la proprietà temporanea: quando un oggetto deve essere accessibile solo se esiste_ e successivamente _Inoltre, std :: weak_ptr è usato per rompere i riferimenti circolari_, quindi almeno lì - e in molti altri posti - vengono espresse opinioni diverse. –

+0

E ho usato la mia reputazione personale per sviare la risposta. Ecco come funziona SO. Inoltre, come si legge "proprietà temporanee dei modelli" come supporto al punto dichiarato di "modo predefinito di esprimere un puntatore non proprietario"? Se possibile, cppreference * contraddice * il tuo post e * supporta * il mio commento. –

0

Esistono diversi tipi di puntatori intelligenti. Nel tuo caso, è chiaro che un puntatore intelligente non è realmente necessario, ma potrebbe comunque fornire alcuni vantaggi.

SomeUtilityClass può essere istanziato con un puntatore nullo

Questo è probabilmente meglio risolti con un assegno nel costruttore, un'eccezione o che indica un errore in qualche altro modo nel caso in cui si ottiene un puntatore NULL come argomento. Riesco a malapena a immaginare come sarebbe utile un puntatore intelligente, a meno che non si utilizzi una specifica classe di puntatore intelligente che non accetta NULL, quindi esegue già il controllo.

L'oggetto puntato può essere cancellato/uscire di portata, senza la SomeUtilityClass se ne accorga

Questo può effettivamente essere risolto con un particolare tipo di puntatori intelligenti, ma poi è necessario che l'oggetto puntato in qualche modo supporti la notifica di distruzione. Uno di questi esempi è la classe QPointer nella libreria Qt, che può solo indicare le istanze QObject, che la notificano quando vengono eliminate, quindi il puntatore intelligente diventa automaticamente NULL quando l'oggetto viene eliminato. Esistono tuttavia alcuni problemi con questo approccio:

  1. È necessario controllare i valori NULL ogni volta che si accede all'oggetto tramite il puntatore intelligente.
  2. Se una smart puntatore punta a un'istanza di una classe, dicono MyClass, estendendo la classe di eseguire la notifica di eliminazione (QObject nel caso Qt), si ottengono risultati strani: è il distruttore di QObject che notifica il puntatore intelligente , quindi è possibile che tu acceda a questo quando il distruttore MyClass ha già iniziato il suo lavoro sporco, quindi l'oggetto è parzialmente distrutto, ma il puntatore non è ancora NULL perché la distruzione è ancora in corso.
+1

Ora, normalmente ignoro i downvotes, ma in questo caso ho risposto chiaramente ai punti indicati dall'OP, fornendo un esempio da una libreria C++ ampiamente utilizzata. Cosa c'è di così sbagliato nella mia risposta che qualcuno abbia sacrificato un intero punto di reputazione solo per dire al mondo quanto sia pericoloso e pericoloso? Forse mi dici e lo aggiusto? –

Problemi correlati