2015-07-15 31 views
18

Questa è una domanda di base, ma non ho trovato un post precedente a riguardo. Il titolo del seguente domanda suona come potrebbe essere la stessa domanda come la mia, ma la domanda stessa non corrisponde al titolo: is it better to use shared_ptr.reset or operator =?std :: shared_ptr: reset() vs. assegnazione

Sono confuso circa lo scopo della funzione reset() membro del std::shared_ptr: che cosa contribuisce oltre all'operatore di assegnazione?

Per essere concreti, data la definizione:

auto p = std::make_shared<int>(1); 
  1. sono le seguenti due linee equivalenti:

    p = std::make_shared<int>(5); 
    p.reset(new int(5)); 
    
  2. Che dire questi:

    p = nullptr; 
    p.reset(); 
    

Se le due linee sono equivalenti in entrambi i casi, qual è lo scopo di reset()?


MODIFICA: Lasciatemi ripetere la domanda per sottolineare meglio il suo punto. La domanda è: c'è un caso in cui lo reset() ci consente di ottenere qualcosa che non è altrettanto facilmente ottenibile senza di esso?

+0

@MeirGoldenberg, chiamando 'reset' su un' shared_ptr' cambia l'oggetto gestito a quello fornito dal puntatore e non dovrebbe essere proprietario di nessun altro 'shared_ptr'.Mentre 'operator =' implicherebbe la condivisione della proprietà con un altro 'shared_ptr' (a meno che, ovviamente, non si stia eseguendo move-assign). – Alejandro

+0

"Non dovrebbe essere di proprietà"? È fatto rispettare o è solo un accordo? Sarebbe bello se questo commento e un esempio diventassero una risposta a tutti gli effetti. – AlwaysLearning

+1

Questo si riduce alla differenza tra la costruzione di un 'shared_ptr' da un puntatore raw e l'uso di' make_shared', che dovrebbe essere banale da scoprire. Suggerimento: c'è una differenza e a volte può essere significativa. –

risposta

11

Quando si utilizza reset() il parametro passato a reset non deve essere necessariamente un oggetto gestito (né può essere); mentre con = il lato destro deve essere un oggetto gestito.

Così queste due righe si danno lo stesso risultato finale:

p = std::make_shared<int>(5); // assign to a newly created shared pointer 
p.reset(new int(5)); // take control of a newly created pointer 

ma non possiamo fare:

p = new int(5); // compiler error no suitable overload 
p.reset(std::make_shared<int>(5).get()); // uh oh undefined behavior 

Senza reset() non sarebbe in grado di riassegnare un puntatore condiviso a un diverso grezzo puntatore senza creare un puntatore condiviso e assegnarlo. Senza = non si sarà in grado di creare un puntatore condiviso su un altro puntatore condiviso.

+2

Capisco il diverso utilizzo. La mia domanda è: c'è un caso in cui 'reset()' mi permette di realizzare qualcosa che non è facilmente realizzabile senza di esso? – AlwaysLearning

+2

Un ptr condiviso creato da 'make_shared' è diverso da quello creato da' .reset (new T) '. – Yakk

1

Non includerò la logica alla base della prima sotto-domanda della differenza tra la costruzione tramite make_shared o da un puntatore, poiché questa differenza è evidenziata in diverse posizioni, tra cui this excellent question.

Tuttavia, penso che sia costruttivo distinguere tra l'utilizzo di reset e operator=. Il primo cede la proprietà della risorsa gestita dallo shared_ptr, distruggendolo se lo shared_ptr è il solo proprietario o diminuendo il conteggio dei riferimenti. Quest'ultimo implica la proprietà condivisa con un altro shared_ptr (a meno che non ti stia muovendo costruendo).

Come ho già detto nei commenti, è importante che il puntatore passato a reset non essere di proprietà di un altro puntatore condivisa o unico, perché produrrebbe un comportamento indefinito sulla distruzione dei due gestori indipendenti - entrambi tenterebbe di delete la risorsa.

Un caso di utilizzo di reset potrebbe essere l'inizializzazione pigra di una risorsa condivisa. Si desidera solo il shared_ptr per gestire alcune risorse, ad esempio la memoria, se proprio ne hai bisogno. Effettuare un'assegnazione definitiva, ad esempio:

std::shared_ptr<resource> shared_resource(new resource(/*construct a resource*/)); 

potrebbe essere uno spreco se non è mai effettivamente necessario. Per fare questo con l'inizializzazione differita, può applicare qualcosa di simile:

std::shared_ptr<resource> shared_resource; 
void use_resource() 
{ 
     if(!shared_resource) 
     { 
      shared_resource.reset(new resource(...)); 
     } 

     shared_resource->do_foo(); 
} 

Utilizzando reset in questo caso è più concisa che fare un swap o l'assegnazione a una temporanea shared_ptr.

+0

"Quest'ultimo [cioè incarico] implica la proprietà condivisa con un altro' shared_ptr' ". Penso che tu voglia contrastarlo con 'reset()', che rende 'p' un proprietario su una risorsa non gestita, che per definizione significa proprietà esclusiva. Più leggo più mi chiedo: chi mai userebbe 'reset()'? C'è un esempio in cui questo è lo strumento da usare? – AlwaysLearning

+0

@ MeirGoldenberg, aggiornato con un potenziale esempio! – Alejandro

+2

Non sono proprio sicuro del perché il tuo esempio non possa essere fatto altrettanto facilmente utilizzando l'operatore di assegnazione. –

0

reset() modifica l'oggetto gestito di uno esistente shared_ptr.

p = std :: shared_ptr (new int (5)); e p.reset (new int (5));

Il primo comporta la creazione di un nuovo shared_ptr e lo spostamento in una variabile. Quest'ultimo non crea un nuovo oggetto, semplicemente modifica il puntatore sottostante gestito dallo shared_ptr.

In altre parole, i due sono pensati per essere utilizzati in diversi casi. L'assegnazione è per quando si dispone di un shared_ptr e reset per quando si dispone di un puntatore raw.

Un'altra cosa da tenere a mente è che shared_ptr era disponibile in modalità boost prima che esistesse l'assegnazione del movimento e influiva pesantemente sull'ultima versione. Senza l'assegnazione del movimento, la possibilità di cambiare un shared_ptr senza fare una copia è utile in quanto consente di risparmiare la contabilità dell'oggetto extra.

+0

'shared_ptr' è esistito solo nello standard da C++ 11: http: //en.cppreference.com/w/cpp/memory/shared_ptr. Boost ha un 'C++ 03' shared_ptr' – NathanOliver

+0

@NathanOliver: ho le mie linee incrociate, riformulato per evitare questa affermazione, ma menziono ancora che avrebbe potuto avere un'influenza. (anche se la capacità generale di apportare una modifica logica senza copiare/spostare è qualcosa che è comune in entrambi i casi) – Guvante

5

È possibile che reset eviti un'assegnazione dinamica della memoria in alcuni casi. Si consideri il codice

std::shared_ptr<int> p{new int{}}; // 1 
p.reset(new int{});     // 2 

On line 1 ci sono 2 allocazioni di memoria dinamica che accadono, uno per l'oggetto int e un secondo per il blocco di controllo 's la shared_ptr che ti terrà traccia del numero di forti riferimenti/deboli all'oggetto gestito.

Sulla riga 2 viene nuovamente assegnata una memoria dinamica per un nuovo oggetto int. All'interno del corpo di reset il shared_ptr determinerà che non ci sono altri riferimenti forti allo int gestito in precedenza, quindi deve essere delete. Dal momento che non ci sono nemmeno riferimenti deboli, potrebbe anche deallocare il blocco di controllo, ma in questo caso sarebbe prudente per l'implementazione riutilizzare lo stesso blocco di controllo perché altrimenti dovrebbe allocarne uno nuovo.

Il comportamento di cui sopra non sarebbe possibile se si dovesse sempre utilizzare l'assegnazione.

std::shared_ptr<int> p{new int{}}; // 1 
p = std::shared_ptr<int>{new int{}}; // 2 

In questo caso, la seconda chiamata al costruttore shared_ptr sulla linea 2 ha già assegnato un blocco di controllo, così p dovrà rilasciare il proprio blocco di controllo esistente.

+0

Suppongo che questo non si applichi quando crei l'originale shared_ptr con make_shared? –

+1

@NirFriedman Meno probabile, ma forse non del tutto irragionevole. Si potrebbe immaginare un'implementazione della libreria che prende una decisione basata su 'sizeof (ControlBlock) + sizeof (T)' che sarebbe più utile evitare l'allocazione dinamica. Potrebbe quindi distruggere l'oggetto, ma non rilasciare il blocco di controllo e diventare proprietario del nuovo puntatore. – Praetorian

+0

In generale, penso che l'uso di 'reset' incoraggia cattive pratiche e dovrebbe essere evitato, ma penso che questa sia una risposta corretta. Ci possono alcuni casi in cui l'uso di 'reset' è un ottimizzazione delle prestazioni, ma vorrei verificare che il miglioramento sia misurabile prima di apportare la modifica. –

Problemi correlati