C ++ - passaggio di riferimenti a std :: shared_ptr o boost :: shared_ptr


115

Se ho una funzione che deve funzionare con a shared_ptr, non sarebbe più efficiente passarle un riferimento (in modo da evitare di copiare l' shared_ptroggetto)? Quali sono i possibili effetti collaterali negativi? Immagino due possibili casi:

1) all'interno della funzione viene creata una copia dell'argomento, come in

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) all'interno della funzione viene utilizzato solo l'argomento, come in

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

Non riesco a vedere in entrambi i casi una buona ragione per passare il boost::shared_ptr<foo>valore anziché il riferimento. Il passaggio per valore incrementerebbe solo "temporaneamente" il conteggio dei riferimenti a causa della copia, quindi lo diminuirà quando si esce dall'ambito della funzione. Sto trascurando qualcosa?

Giusto per chiarire, dopo aver letto diverse risposte: sono perfettamente d'accordo sui problemi di ottimizzazione prematura e cerco sempre di prima-profilo-poi-lavorare-sui-punti caldi. La mia domanda era più da un punto di vista del codice puramente tecnico, se sai cosa intendo.


Non so se puoi modificare i tag della tua domanda, ma prova ad aggiungere un tag boost lì. Ho provato a cercare questa domanda ma non sono riuscita a trovarne nessuna perché cercavo tag boost e smart-pointer. Quindi ho trovato la tua domanda subito dopo aver composto la mia domanda
Edison Gustavo Muenz

Risposte:


113

Lo scopo di shared_ptrun'istanza distinta è garantire (per quanto possibile) che finché questo shared_ptrè nell'ambito, l'oggetto a cui punta esisterà ancora, perché il suo conteggio di riferimento sarà almeno 1.

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

Quindi, utilizzando un riferimento a a shared_ptr, disabiliti tale garanzia. Quindi nel tuo secondo caso:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

Come fai a sapere che sp->do_something()non esploderà a causa di un puntatore nullo?

Tutto dipende da cosa c'è in quelle sezioni "..." del codice. E se chiamassi qualcosa durante il primo '...' che ha l'effetto collaterale (da qualche parte in un'altra parte del codice) di cancellare a shared_ptrsu quello stesso oggetto? E se fosse l'unico che rimane distinto shared_ptrda quell'oggetto? Ciao ciao oggetto, proprio dove stai per provare a usarlo.

Quindi ci sono due modi per rispondere a questa domanda:

  1. Esamina molto attentamente l'origine dell'intero programma finché non sei sicuro che l'oggetto non morirà durante il corpo della funzione.

  2. Modificare il parametro in modo che sia un oggetto distinto anziché un riferimento.

Un consiglio generale che si applica qui: non preoccuparti di apportare modifiche rischiose al tuo codice per motivi di prestazioni finché non hai programmato il tuo prodotto in una situazione realistica in un profiler e hai misurato in modo definitivo che il cambiamento che vuoi fare farà un differenza significativa rispetto alle prestazioni.

Aggiornamento per il commentatore JQ

Ecco un esempio artificioso. È deliberatamente semplice, quindi l'errore sarà ovvio. In esempi reali, l'errore non è così ovvio perché è nascosto in strati di dettagli reali.

Abbiamo una funzione che invierà un messaggio da qualche parte. Potrebbe essere un messaggio di grandi dimensioni, quindi invece di utilizzare un std::stringche probabilmente viene copiato mentre viene passato in più posizioni, utilizziamo shared_ptra una stringa:

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(Per questo esempio lo "inviamo" alla console).

Ora vogliamo aggiungere una funzione per ricordare il messaggio precedente. Vogliamo il seguente comportamento: deve esistere una variabile che contiene il messaggio inviato più di recente, ma mentre un messaggio è attualmente in fase di invio, non deve esserci alcun messaggio precedente (la variabile deve essere ripristinata prima dell'invio). Quindi dichiariamo la nuova variabile:

std::shared_ptr<std::string> previous_message;

Quindi modifichiamo la nostra funzione in base alle regole che abbiamo specificato:

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

Quindi, prima di iniziare a inviare, scartiamo il messaggio precedente corrente e quindi, dopo che l'invio è completo, possiamo memorizzare il nuovo messaggio precedente. Tutto bene. Ecco un po 'di codice di prova:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

E come previsto, viene stampato Hi!due volte.

Ora arriva il signor Maintainer, che guarda il codice e pensa: Ehi, quel parametro send_messageè a shared_ptr:

void send_message(std::shared_ptr<std::string> msg)

Ovviamente può essere modificato in:

void send_message(const std::shared_ptr<std::string> &msg)

Pensa al miglioramento delle prestazioni che questo porterà! (Non importa che stiamo per inviare un messaggio tipicamente grande su qualche canale, quindi il miglioramento delle prestazioni sarà così piccolo da non essere misurabile).

Ma il vero problema è che ora il codice di test mostrerà un comportamento indefinito (nelle build di debug di Visual C ++ 2010, si arresta in modo anomalo).

Il signor Maintainer è sorpreso da questo, ma aggiunge un controllo difensivo send_messagenel tentativo di impedire che il problema si verifichi:

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

Ma ovviamente va ancora avanti e va in crash, perché msgnon è mai nullo quando send_messageviene chiamato.

Come ho detto, con tutto il codice così vicino in un banale esempio, è facile trovare l'errore. Ma nei programmi reali, con relazioni più complesse tra oggetti mutabili che mantengono puntatori l'uno all'altro, è facile commettere l'errore e difficile costruire i casi di test necessari per rilevare l'errore.

La soluzione facile, in cui si desidera che una funzione possa fare affidamento sul fatto che shared_ptrcontinui a essere non nulla per tutto il tempo, è che la funzione allochi il proprio vero shared_ptr, piuttosto che fare affidamento su un riferimento a un esistente shared_ptr.

Lo svantaggio è che la copia di a shared_ptrnon è gratuita: anche le implementazioni "lock-free" devono utilizzare un'operazione interlock per rispettare le garanzie di threading. Quindi ci possono essere situazioni in cui un programma può essere notevolmente velocizzato cambiando un shared_ptrin a shared_ptr &. Ma non si tratta di una modifica che può essere apportata in sicurezza a tutti i programmi. Cambia il significato logico del programma.

Nota che si verificherebbe un bug simile se usassimo std::stringtutto invece di std::shared_ptr<std::string>e invece di:

previous_message = 0;

per chiarire il messaggio, abbiamo detto:

previous_message.clear();

Quindi il sintomo sarebbe l'invio accidentale di un messaggio vuoto, invece di un comportamento indefinito. Il costo di una copia extra di una stringa molto grande può essere molto più significativo del costo della copia di a shared_ptr, quindi il compromesso potrebbe essere diverso.


16
Il shared_ptr passato è già presente in un ambito, nel sito della chiamata. Potresti essere in grado di creare uno scenario elaborato in cui il codice in questa domanda potrebbe saltare in aria a causa di un puntatore penzolante, ma suppongo che tu abbia problemi più grandi del parametro di riferimento!
Magnus Hoff

10
Può essere memorizzato in un membro. Puoi chiamare qualcosa che accade per cancellare quel membro. Il punto centrale di smart_ptr è evitare di dover coordinare le vite in gerarchie o ambiti che si annidano attorno allo stack di chiamate, quindi è meglio presumere che le vite non lo facciano in tali programmi.
Daniel Earwicker

7
Non è proprio il mio punto di vista però! Se pensi che quello che sto dicendo sia qualcosa di specifico che ha a che fare con il mio codice, potresti non avermi capito. Sto parlando di un'inevitabile implicazione del motivo per cui shared_ptr esiste in primo luogo: molte vite di oggetti non sono semplicemente correlate alle chiamate di funzione.
Daniel Earwicker

8
@DanielEarwicker è completamente d'accordo con tutti i tuoi punti e sono sorpreso dal livello di opposizione. Qualcosa che rende le tue preoccupazioni ancora più rilevanti è il threading, quando questo viene coinvolto le garanzie sulla validità di un oggetto diventano molto più importanti. Buona risposta.
radman

3
Non molto tempo fa, ho individuato un bug molto grave dovuto al passaggio di un riferimento a un puntatore condiviso. Il codice stava gestendo il cambio di stato di un oggetto e quando ha notato che lo stato dell'oggetto era cambiato, lo ha rimosso dalla raccolta di oggetti nello stato precedente e lo ha spostato nella raccolta di oggetti nel nuovo stato. L'operazione di rimozione ha distrutto l'ultimo puntatore condiviso all'oggetto. La funzione membro era stata chiamata su un riferimento al puntatore condiviso nella raccolta. Boom. Daniel Earwicker ha ragione.
David Schwartz

115

Mi sono trovato in disaccordo con la risposta più votata, quindi sono andato alla ricerca di opinioni di esperti ed eccole qui. Da http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: "quando passi shared_ptrs, le copie sono costose"

Scott Meyers: "Non c'è niente di speciale in shared_ptr quando si tratta di passarlo per valore o per riferimento. Usa esattamente la stessa analisi che usi per qualsiasi altro tipo definito dall'utente. Le persone sembrano avere questa percezione che shared_ptr in qualche modo risolve tutti i problemi di gestione e che, poiché è piccolo, è necessariamente poco costoso passare per valore. Deve essere copiato, e c'è un costo associato a questo ... è costoso passare per valore, quindi se posso farla franca con la semantica corretta nel mio programma, lo passerò invece per riferimento a const o riferimento "

Herb Sutter: "passali sempre per riferimento a const, e molto occasionalmente forse perché sai come hai chiamato potrebbe modificare la cosa da cui hai ottenuto un riferimento, forse allora potresti passare per valore ... se li copi come parametri, oh mio Dio, non hai quasi mai bisogno di aumentare il numero di riferimenti perché è comunque tenuto in vita e dovresti passarlo per riferimento, quindi fallo "

Aggiornamento: Herb ha ampliato questo argomento qui: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ , anche se la morale della storia è che non dovresti passare shared_ptrs at all "a meno che non si desideri utilizzare o manipolare il puntatore intelligente stesso, ad esempio per condividere o trasferire la proprietà."


8
Bella scoperta! È bello vedere due dei massimi esperti in materia che confutano pubblicamente la saggezza convenzionale su SO.
Stephan Tolksdorf

3
"Non c'è niente di speciale in shared_ptr quando si tratta di passarlo per valore o per riferimento" - Non sono davvero d'accordo con questo. È speciale. Personalmente preferirei andare sul sicuro e prendere il leggero colpo di prestazioni. Se c'è una particolare area di codice che devo ottimizzare, certo, guarderei ai vantaggi in termini di prestazioni di shared_ptr pass per const ref.
JasonZ

3
È anche interessante notare che, mentre c'era un accordo sull'uso eccessivo di shared_ptrs, non c'era accordo sulla questione del passaggio per valore rispetto al riferimento.
Nicol Bolas

2
Scott Meyers: "quindi se riesco a farla franca con una semantica appropriata nel mio programma ..." cioè non contraddice affatto la mia risposta, il che indica che capire se la modifica dei parametri const &influirà sulla semantica è facile solo in molto programmi semplici.
Daniel Earwicker

7
Herb Sutter: "molto occasionalmente forse perché sai come hai chiamato potrebbe modificare la cosa da cui hai avuto un riferimento". Ancora una volta, quella piccola esenzione per un caso minore, quindi non contraddice la mia risposta. La domanda rimane: come fai a sapere che è sicuro usare un const ref? Molto facile da provare in un programma semplice, non così facile in un programma complesso. Ma ehi, questo è C ++, quindi preferiamo la microottimizzazione prematura su quasi tutti gli altri problemi di ingegneria, giusto ?! :)
Daniel Earwicker

22

Vorrei sconsigliare questa pratica a meno che tu e gli altri programmatori con cui lavori non sappia davvero cosa stai facendo.

Primo, non hai idea di come potrebbe evolversi l'interfaccia della tua classe e vuoi impedire ad altri programmatori di fare cose cattive. Passare un shared_ptr per riferimento non è qualcosa che un programmatore dovrebbe aspettarsi di vedere, perché non è idiomatico e questo lo rende facile da usare in modo errato. Programmare in modo difensivo: rendere l'interfaccia difficile da usare in modo errato. Passare per riferimento inviterà i problemi in seguito.

Secondo, non ottimizzare fino a quando non sai che questa particolare classe rappresenterà un problema. Prima il profilo, quindi se il tuo programma ha davvero bisogno della spinta data passando per riferimento, allora forse. Altrimenti, non preoccuparti per le piccole cose (cioè le N istruzioni extra necessarie per passare in base al valore) invece preoccuparti del design, delle strutture dei dati, degli algoritmi e della manutenibilità a lungo termine.


Sebbene la risposta di litb sia tecnicamente corretta, non sottovalutare mai la "pigrizia" dei programmatori (anch'io sono pigro!). La risposta di littlenag è migliore, che un riferimento a shared_ptr sarà inaspettato e forse (probabilmente) un'ottimizzazione non necessaria che rende la manutenzione futura più impegnativa.
netjeff

18

Sì, prendere un riferimento va bene lì. Non intendi dare al metodo la proprietà condivisa; vuole solo lavorarci. Potresti prendere un riferimento anche per il primo caso, dal momento che lo copi comunque. Ma per il primo caso, prende la proprietà. C'è questo trucco per copiarlo ancora una volta sola:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

Dovresti anche copiare quando lo restituisci (cioè non restituire un riferimento). Perché la tua classe non sa cosa sta facendo il cliente (potrebbe memorizzare un puntatore ad esso e poi si verifica il big bang). Se in seguito si scopre che si tratta di un collo di bottiglia (primo profilo!), Puoi comunque restituire un riferimento.


Modifica : Naturalmente, come altri sottolineano, questo è vero solo se conosci il tuo codice e sai che non reimposti in qualche modo il puntatore condiviso passato. In caso di dubbio, passa semplicemente per valore.


11

È ragionevole passare shared_ptrs da const&. Probabilmente non causerà problemi (tranne nel caso improbabile in cui il riferimento shared_ptrviene eliminato durante la chiamata di funzione, come spiegato in dettaglio da Earwicker) e probabilmente sarà più veloce se si passano molti di questi in giro. Ricorda; il valore predefinito boost::shared_ptrè thread-safe, quindi la copia include un incremento thread-safe.

Prova a usare const&piuttosto che solo &, perché gli oggetti temporanei potrebbero non essere passati per riferimento non const. (Anche se un'estensione della lingua in MSVC ti consente di farlo comunque)


3
Sì, uso sempre i riferimenti const, ho solo dimenticato di metterlo nel mio esempio. In ogni caso, MSVC consente di associare riferimenti non cost a temporanei non per un bug, ma perché di default ha la proprietà "C / C ++ -> Lingua -> Disabilita estensione lingua" impostata su "NO".
Abilitalo

abigagli: Sul serio? Dolce! Lo farò rispettare al lavoro, come prima cosa domani;)
Magnus Hoff

10

Nel secondo caso, farlo è più semplice:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

Puoi chiamarlo come

only_work_with_sp(*sp);

3
Se adotti la convenzione di usare riferimenti a oggetti quando non hai bisogno di prendere una copia del puntatore, serve anche a documentare il tuo intento. Ti dà anche la possibilità di usare un riferimento const.
Mark Ransom

Sì, sono d'accordo sull'uso di riferimenti a oggetti come mezzo per esprimere che la funzione chiamata non "ricorda" nulla di quell'oggetto. Di solito utilizzo argomenti formali del puntatore se la funzione "tiene traccia" dell'oggetto
abigagli

3

Eviterei un riferimento "semplice" a meno che la funzione non possa modificare esplicitamente il puntatore.

A const &può essere una microottimizzazione sensata quando si richiamano piccole funzioni, ad esempio per consentire ulteriori ottimizzazioni, come l'inserimento di alcune condizioni. Inoltre, l'incremento / decremento, poiché è thread-safe, è un punto di sincronizzazione. Tuttavia, non mi aspetto che questo faccia una grande differenza nella maggior parte degli scenari.

In generale, dovresti usare lo stile più semplice a meno che tu non abbia motivo di non farlo. Quindi, usa const &coerentemente o aggiungi un commento sul perché se lo usi solo in alcuni punti.


3

Suggerirei di passare un puntatore condiviso tramite riferimento const - una semantica che la funzione passata con il puntatore NON possiede il puntatore, che è un linguaggio pulito per gli sviluppatori.

L'unico problema è che in più programmi thread l'oggetto puntato dal puntatore condiviso viene distrutto in un altro thread. Quindi è sicuro dire che l'uso del riferimento const del puntatore condiviso è sicuro in un programma a thread singolo.

Il passaggio di un puntatore condiviso tramite un riferimento non const a volte è pericoloso - il motivo sono le funzioni di swap e reset che la funzione può invocare all'interno in modo da distruggere l'oggetto che è ancora considerato valido dopo il ritorno della funzione.

Non si tratta di un'ottimizzazione prematura, immagino: si tratta di evitare inutili sprechi di cicli della CPU quando hai chiaro cosa vuoi fare e l'idioma di codifica è stato fermamente adottato dai tuoi colleghi sviluppatori.

Solo i miei 2 centesimi :-)


1
Vedi il commento sopra di David Schwartz "... Ho inseguito un bug molto serio dovuto al passaggio di un riferimento a un puntatore condiviso. Il codice stava gestendo il cambiamento di stato di un oggetto e quando ha notato che lo stato dell'oggetto era cambiato, lo ha rimosso dalla raccolta di oggetti nello stato precedente e lo ha spostato nella raccolta di oggetti nel nuovo stato. L'operazione di rimozione ha distrutto l'ultimo puntatore condiviso all'oggetto. La funzione membro era stata chiamata su un riferimento al puntatore condiviso nella raccolta. Boom ... "
Jason Harrison

3

Sembra che tutti i pro ei contro qui possano effettivamente essere generalizzati a QUALSIASI tipo passato per riferimento, non solo shared_ptr. Secondo me, dovresti conoscere la semantica del passaggio per riferimento, riferimento const e valore e usarlo correttamente. Ma non c'è assolutamente nulla di intrinsecamente sbagliato nel passare shared_ptr per riferimento, a meno che tu non pensi che tutti i riferimenti siano cattivi ...

Per tornare all'esempio:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

Come fai a sapere che sp.do_something()non esploderà a causa di un puntatore penzolante?

La verità è che, shared_ptr o no, const o no, questo potrebbe accadere se hai un difetto di progettazione, come condividere direttamente o indirettamente la proprietà di sptra thread, perdere un oggetto che lo fa delete this, hai una proprietà circolare o altri errori di proprietà.


2

Una cosa che non ho ancora visto menzionata è che quando si passano puntatori condivisi per riferimento, si perde la conversione implicita che si ottiene se si desidera passare un puntatore condiviso alla classe derivata tramite un riferimento a un puntatore condiviso alla classe base.

Ad esempio, questo codice produrrà un errore, ma funzionerà se si modifica in test()modo che il puntatore condiviso non venga passato per riferimento.

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}

1

Presumo che tu abbia familiarità con l'ottimizzazione prematura e lo chiedi per scopi accademici o perché hai isolato del codice preesistente che ha prestazioni insufficienti.

Passare per riferimento va bene

Il passaggio per riferimento const è migliore e di solito può essere utilizzato, poiché non forza const-ness sull'oggetto puntato.

Non si corre il rischio di perdere il puntatore a causa dell'utilizzo di un riferimento. Quel riferimento è la prova che hai una copia del puntatore intelligente in precedenza nello stack e solo un thread possiede uno stack di chiamate, in modo che la copia preesistente non scompaia.

L'uso dei riferimenti è spesso più efficiente per i motivi menzionati, ma non è garantito . Ricorda che anche la dereferenziazione di un oggetto può richiedere del lavoro. Il tuo scenario di utilizzo di riferimento ideale sarebbe se il tuo stile di codifica coinvolgesse molte piccole funzioni, in cui il puntatore verrebbe passato da una funzione all'altra prima di essere utilizzato.

Dovresti sempre evitare di memorizzare il tuo puntatore intelligente come riferimento. Il tuo Class::take_copy_of_sp(&sp)esempio mostra l'utilizzo corretto per questo.


1
"Non si corre il rischio di perdere il puntatore a causa dell'utilizzo di un riferimento. Quel riferimento è la prova che si dispone di una copia del puntatore intelligente all'inizio dello stack" O un membro dei dati ...?
Daniel Earwicker

Considera la magia di boost :: thread e boost :: ref: boost :: function <int> functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = nuovo boost :: thread (functionPointer); ... elimina sharedPtrInstance
Jason Harrison

1

Supponendo che non siamo interessati alla correttezza const (o più, intendi consentire alle funzioni di essere in grado di modificare o condividere la proprietà dei dati che vengono passati), passare un boost :: shared_ptr per valore è più sicuro che passarlo per riferimento come permettiamo al boost :: shared_ptr originale di controllare la propria durata. Considera i risultati del codice seguente ...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

Dall'esempio sopra vediamo che il passaggio di sharedA per riferimento consente a FooTakesReference di reimpostare il puntatore originale, il che riduce il suo numero di utilizzo a 0, distruggendo i suoi dati. FooTakesValue , tuttavia, non può reimpostare il puntatore originale, garantendo che i dati di sharedB siano ancora utilizzabili. Quando un altro sviluppatore inevitabilmente arriva e tenta di cavalcare la fragile esistenza di sharedA , ne consegue il caos. Il fortunato sviluppatore di sharedB , tuttavia, torna a casa presto perché nel suo mondo va tutto bene.

La sicurezza del codice, in questo caso, supera di gran lunga qualsiasi miglioramento della velocità creato dalla copia. Allo stesso tempo, il boost :: shared_ptr ha lo scopo di migliorare la sicurezza del codice. Sarà molto più facile passare da una copia a un riferimento, se qualcosa richiede questo tipo di ottimizzazione di nicchia.


1

Sandy ha scritto: "Sembra che tutti i pro ei contro qui possano effettivamente essere generalizzati a QUALSIASI tipo passato per riferimento non solo a shared_ptr."

Vero in una certa misura, ma il punto di utilizzare shared_ptr è eliminare le preoccupazioni riguardanti la durata degli oggetti e lasciare che sia il compilatore a gestirlo per te. Se si intende passare un puntatore condiviso per riferimento e consentire ai client dei metodi non const di chiamare l'oggetto con conteggio dei riferimenti che potrebbero liberare i dati dell'oggetto, utilizzare un puntatore condiviso è quasi inutile.

Ho scritto "quasi" in quella frase precedente perché le prestazioni possono essere un problema, e "potrebbe" essere giustificato in rari casi, ma eviterei anch'io questo scenario e cercherò io stesso tutte le altre possibili soluzioni di ottimizzazione, ad esempio da guardare seriamente all'aggiunta di un altro livello di riferimento indiretto, valutazione pigra, ecc.

Il codice che esiste dopo il suo autore, o anche dopo la sua memoria dell'autore, che richiede presupposti impliciti sul comportamento, in particolare il comportamento sulla durata degli oggetti, richiede una documentazione chiara, concisa e leggibile, e quindi molti client non lo leggeranno comunque! La semplicità ha quasi sempre la meglio sull'efficienza e ci sono quasi sempre altri modi per essere efficienti. Se hai davvero bisogno di passare i valori per riferimento per evitare la copia profonda da parte dei costruttori di copia dei tuoi oggetti conteggiati di riferimento (e dell'operatore uguale), allora forse dovresti considerare i modi per rendere i dati copiati in profondità come puntatori conteggiati di riferimento che possono essere copiato rapidamente. (Ovviamente, questo è solo uno scenario di progettazione che potrebbe non essere applicabile alla tua situazione).


1

Lavoravo in un progetto in cui il principio era molto forte sul passaggio di puntatori intelligenti per valore. Quando mi è stato chiesto di fare un'analisi delle prestazioni, ho scoperto che per l'incremento e il decremento dei contatori di riferimento dei puntatori intelligenti l'applicazione trascorre tra il 4-6% del tempo di elaborazione utilizzato.

Se vuoi passare i puntatori intelligenti per valore solo per evitare problemi in casi strani come descritto da Daniel Earwicker assicurati di capire il prezzo che stai pagando per questo.

Se si decide di utilizzare un riferimento, il motivo principale per utilizzare il riferimento const è rendere possibile l'upcasting implicito quando è necessario passare un puntatore condiviso a un oggetto dalla classe che eredita la classe utilizzata nell'interfaccia.


0

In aggiunta a quanto detto da litb, vorrei far notare che probabilmente nel secondo esempio deve passare per riferimento const, in questo modo sei sicuro di non modificarlo accidentalmente.


0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. passare per valore:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
  2. passare per riferimento:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
  3. passare per puntatore:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }

0

Ogni pezzo di codice deve avere un senso. Se si passa un puntatore condiviso per valore ovunque nell'applicazione, significa "Non sono sicuro di cosa sta succedendo altrove, quindi preferisco la sicurezza grezza ". Questo non è quello che io chiamo un buon segno di fiducia per altri programmatori che potrebbero consultare il codice.

Ad ogni modo, anche se una funzione riceve un riferimento const e non sei "sicuro", puoi comunque creare una copia del puntatore condiviso all'inizio della funzione, per aggiungere un riferimento forte al puntatore. Questo potrebbe anche essere visto come un suggerimento sul design ("il puntatore potrebbe essere modificato altrove").

Quindi sì, IMO, l'impostazione predefinita dovrebbe essere " passa per riferimento const ".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.