std :: shared_ptr come ultima risorsa?


60

Stavo solo guardando gli stream di "Going Native 2012" e ho notato la discussione std::shared_ptr. Sono stato un po 'sorpreso di sentire l'opinione in qualche modo negativa di Bjarne std::shared_ptre il suo commento sul fatto che dovrebbe essere usato come "ultima risorsa" quando la vita di un oggetto è incerta (cosa che, secondo lui, dovrebbe raramente essere il caso).

Qualcuno vorrebbe spiegarlo un po 'più in profondità? Come possiamo programmare senza std::shared_ptre ancora gestire la durata degli oggetti in modo sicuro ?


8
Non usi i puntatori? Avere un proprietario distinto dell'oggetto, che gestisce la vita?
Bo Persson,

2
che dire dei dati esplicitamente condivisi? È difficile non usare i puntatori. Anche std :: shared_pointer farebbe la sporca "gestione della vita" in quel caso
Kamil Klimek,

6
Hai considerato di ascoltare meno i consigli presentati e di più l'argomento alla base di tali consigli? Spiega abbastanza bene il tipo di sistema in cui questo tipo di consiglio funzionerebbe.
Nicol Bolas,

@NicolBolas: ho ascoltato il consiglio e l'argomento, ma ovviamente non pensavo di averlo capito abbastanza bene.
ronag

A che ora dice "ultima risorsa"? Guardando il bit a 36 minuti in ( channel9.msdn.com/Events/GoingNative/GoingNative-2012/… ) dice che è diffidente nell'uso dei puntatori, ma significa in generale puntatori, non solo shared_ptr e unique_ptr ma anche ' puntatore regolare. Implica che gli oggetti stessi (e non i puntatori agli oggetti allocati con nuovi) dovrebbero essere preferiti. La parte a cui stavi pensando più avanti nella presentazione?
Pharap,

Risposte:


56

Se riesci ad evitare la proprietà condivisa, la tua applicazione sarà più semplice e facile da capire e quindi meno suscettibile ai bug introdotti durante la manutenzione. I modelli di proprietà complessi o poco chiari tendono a creare accoppiamenti difficili da seguire di diverse parti dell'applicazione attraverso uno stato condiviso che potrebbero non essere facilmente rintracciabili.

Detto questo, è preferibile usare oggetti con durata di memorizzazione automatica e avere oggetti secondari "di valore". In caso contrario , unique_ptrpotrebbe essere una buona alternativa shared_ptrall'essere - se non all'ultima risorsa - in qualche modo in fondo alla lista degli strumenti desiderabili.


5
+1 per aver scoperto che il problema non è la stessa techno (proprietà condivisa), ma le difficoltà che introduce per noi semplici umani che devono quindi decifrare ciò che sta accadendo.
Matthieu M.

Tuttavia, adottare tale approccio limiterà gravemente la capacità di un programmatore di applicare modelli di programmazione di concorrenza sulla maggior parte delle classi OOP non banali (a causa della non copiabilità). Questo problema è stato sollevato in "Going Native 2013".
rwong,

48

Il mondo in cui vive Bjarne è molto ... accademico, per mancanza di un termine migliore. Se il tuo codice può essere progettato e strutturato in modo tale che gli oggetti abbiano gerarchie relazionali molto deliberate, in modo tale che le relazioni di proprietà siano rigide e inflessibili, il codice scorre in una direzione (da livello alto a livello basso) e gli oggetti parlano solo a quelli inferiori la gerarchia, quindi non troverai molto bisogno shared_ptr. È qualcosa che usi in quelle rare occasioni in cui qualcuno deve infrangere le regole. Altrimenti, puoi semplicemente inserire tutto in vectors o altre strutture di dati che usano la semantica di valore e unique_ptrs per cose che devi allocare singolarmente.

Anche se è un mondo fantastico in cui vivere, non è quello che devi fare tutto il tempo. Se non riesci a organizzare il tuo codice in quel modo, perché la progettazione del sistema che stai cercando di realizzare significa che è impossibile (o semplicemente profondamente spiacevole), allora ti ritroverai sempre più a dover condividere la proprietà degli oggetti .

In un tale sistema, tenere puntatori nudi non è ... esattamente pericoloso, ma solleva domande. La cosa grandiosa shared_ptrè che fornisce ragionevoli garanzie sintattiche sulla durata dell'oggetto. Può essere rotto? Ovviamente. Ma le persone possono anche const_castcose; le cure di base e l'alimentazione shared_ptrdovrebbero fornire una ragionevole qualità di vita per gli oggetti assegnati i cui proprietari devono essere condivisi.

Quindi, ci sono weak_ptrs, che non possono essere usati in assenza di a shared_ptr. Se il tuo sistema è rigidamente strutturato, puoi archiviare un puntatore nudo su un oggetto, con la certezza che la struttura dell'applicazione garantisce che l'oggetto puntato ti sopravviverà. È possibile chiamare una funzione che restituisce un puntatore a un valore interno o esterno (ad esempio trova un oggetto chiamato X). Nel codice adeguatamente strutturato, tale funzione sarebbe disponibile solo se la durata dell'oggetto fosse garantita superiore a quella dell'utente; quindi, archiviare quel puntatore nudo nel tuo oggetto va bene.

Dal momento che quella rigidità non è sempre possibile raggiungere nei sistemi reali, è necessario un modo per garantire ragionevolmente la durata. A volte, non è necessaria la piena proprietà; a volte, devi solo essere in grado di sapere quando il puntatore è cattivo o buono. Ecco dove weak_ptrentra in gioco. Ci sono stati casi in cui avrei potuto usare un unique_ptro boost::scoped_ptr, ma ho dovuto usare un shared_ptrperché avevo specificamente bisogno di dare a qualcuno un puntatore "volatile". Un puntatore la cui durata era indeterminata e potevano interrogare quando quel puntatore veniva distrutto.

Un modo sicuro per sopravvivere quando lo stato del mondo è indeterminato.

Potrebbe essere stato fatto da qualche chiamata di funzione per ottenere il puntatore, anziché tramite weak_ptr? Sì, ma potrebbe essere più facilmente risolto. Una funzione che restituisce un puntatore nudo non ha modo di suggerire sintatticamente che l'utente non faccia qualcosa come archiviare quel puntatore a lungo termine. Restituire a shared_ptrrende anche troppo facile per qualcuno semplicemente memorizzarlo e potenzialmente prolungare la durata di vita di un oggetto. Restituire un weak_ptrcomunque suggerisce fortemente che la memorizzazione di ciò shared_ptrche ottieni lockè un'idea ... dubbia. Non ti impedirà di farlo, ma nulla in C ++ ti impedisce di violare il codice. weak_ptrfornisce una resistenza minima dal fare la cosa naturale.

Ora, questo non vuol dire che shared_ptrnon può essere abusato ; certamente può. Soprattutto pre unique_ptr, ci sono stati molti casi in cui ho appena usato un boost::shared_ptrperché avevo bisogno di passare un puntatore RAII o inserirlo in un elenco. Senza mossa semantica e unique_ptr, boost::shared_ptrera l'unica vera soluzione.

E puoi usarlo in luoghi in cui è del tutto superfluo. Come detto sopra, un'adeguata struttura del codice può eliminare la necessità di alcuni usi di shared_ptr. Ma se il tuo sistema non può essere strutturato come tale e continua a fare ciò di cui ha bisogno, shared_ptrsarà di grande utilità.


4
+1: guarda ad es. Boost :: asio. Penso che l'idea si estenda in molte aree, potresti non sapere al momento della compilazione quale widget dell'interfaccia utente o chiamata asincrona è l'ultimo a rinunciare a un oggetto, e con shared_ptr non hai bisogno di sapere. Ovviamente non si applica a tutte le situazioni, solo un altro strumento (molto utile) nella cassetta degli attrezzi.
Guy Sirton

3
Un commento un po 'in ritardo; shared_ptrè ottimo per i sistemi in cui c ++ è integrato con un linguaggio di scripting come Python. Usando boost::python, il conteggio dei riferimenti sul lato c ++ e python collabora notevolmente; qualsiasi oggetto di c ++ può ancora essere tenuto in Python e non morirà.
eudoxos,

1
Solo per riferimento la mia comprensione non è né l'uso di WebKit né di Chromium shared_ptr. Entrambi usano le proprie implementazioni di intrusive_ptr. Lo
sollevo

1
@gman: Trovo il tuo commento molto fuorviante, dal momento che l'obiezione di Stroustrup si shared_ptrapplica ugualmente a intrusive_ptr: si oppone all'intero concetto di proprietà condivisa, non a uno specifico spelling del concetto. Quindi, ai fini della presente domanda, questi sono due esempi reali di grandi applicazioni che fanno uso shared_ptr. (E, per di più, dimostrano che shared_ptrè utile anche quando non lo consente weak_ptr.)
ruakh

1
FWIW, per contrastare l'affermazione che Bjarne vive nel mondo accademico: in tutta la mia carriera puramente industriale (che includeva la co-progettazione di una borsa del G20 e la sola architettura di un MOG da 500K giocatori) ho visto solo 3 casi in cui ne avevamo davvero bisogno proprietà condivisa. Sono al 200% con Bjarne qui.
No-Bugs Hare

38

Non credo di aver mai usato std::shared_ptr.

Il più delle volte, un oggetto è associato ad una collezione, alla quale appartiene per tutta la sua vita. Nel qual caso puoi semplicemente usare whatever_collection<o_type>o whatever_collection<std::unique_ptr<o_type>>, quella raccolta essendo un membro di un oggetto o una variabile automatica. Naturalmente, se non avessi bisogno di un numero dinamico di oggetti, potresti semplicemente utilizzare un array automatico di dimensioni fisse.

Né l'iterazione tramite la raccolta né qualsiasi altra operazione sull'oggetto richiede una funzione di supporto per condividere la proprietà ... utilizza l'oggetto, quindi ritorna e il chiamante garantisce che l'oggetto rimane attivo per l'intera chiamata . Questo è di gran lunga il contratto più utilizzato tra chiamante e chiamante.


Nicol Bolas ha commentato che "Se un oggetto si regge su un puntatore nudo e quell'oggetto muore ... oops". e "Gli oggetti devono garantire che l'oggetto viva attraverso la vita di quell'oggetto. Solo shared_ptrpuò farlo."

Non compro quell'argomento. Almeno non shared_ptrrisolve questo problema. Che dire:

  • Se una tabella hash si regge su un oggetto e il codice hash di quell'oggetto cambia ... oops.
  • Se una funzione sta ripetendo un vettore e un elemento viene inserito in quel vettore ... oops.

Come la garbage collection, l'uso predefinito di shared_ptrincoraggia il programmatore a non pensare al contratto tra oggetti o tra funzione e chiamante. È necessario pensare a precondizioni e postcondizioni corrette e la durata dell'oggetto è solo una piccola parte di quella torta più grande.

Gli oggetti non "muoiono", un pezzo di codice li distrugge. E lanciare shared_ptril problema invece di capire il contratto di chiamata è una falsa sicurezza.


17
@ronag: sospetto che tu abbia iniziato a usarlo dove un puntatore non elaborato sarebbe stato migliore, perché "i puntatori non elaborati sono errati". Ma i puntatori grezzi non sono male . Fare solo il primo puntatore proprietario di un oggetto come un puntatore non elaborato è negativo, perché in questo caso è necessario gestire manualmente la memoria, che è banale in presenza di eccezioni. Ma usare puntatori non elaborati come handle o iteratori va bene.
Ben Voigt,

4
@BenVoigt: Certo, la difficoltà a passare in giro puntatori nudi è che non conosci la vita degli oggetti. Se un oggetto si blocca su un puntatore nudo e quell'oggetto muore ... oops. Questo è esattamente il genere di cose shared_ptrche weak_ptrsono state progettate per evitare. Bjarne cerca di vivere in un mondo in cui tutto ha una vita piacevole ed esplicita, e tutto è costruito attorno a quello. E se riesci a costruire quel mondo, fantastico. Ma non è così nel mondo reale. Gli oggetti devono garantire che l'oggetto viva attraverso la sua vita. Solo shared_ptrpuò farlo.
Nicol Bolas,

5
@NicolBolas: questa è falsa sicurezza. Se il chiamante di una funzione non fornisce la consueta garanzia: "Questo oggetto non verrà toccato da nessuna parte esterna durante la chiamata di funzione", allora entrambi devono concordare quale tipo di modifiche esterne sono consentite. shared_ptrmitiga solo una specifica modifica esterna e nemmeno la più comune. E non è responsabilità dell'oggetto assicurarsi che la sua durata sia corretta, se il contratto di chiamata di funzione specifica diversamente.
Ben Voigt,

6
@NicolBolas: se una funzione crea un oggetto e lo restituisce tramite puntatore, dovrebbe essere a unique_ptr, esprimendo che esiste un solo puntatore all'oggetto e che ha la proprietà.
Ben Voigt,

6
@Nicol: se sta cercando un puntatore in una raccolta, probabilmente dovrebbe usare qualunque tipo di puntatore in quella raccolta, o un puntatore non elaborato se la raccolta contiene valori. Se sta creando un oggetto e il chiamante desidera un shared_ptr, dovrebbe comunque restituire un unique_ptr. La conversione da unique_ptra shared_ptrè facile, ma il contrario è logicamente impossibile.
Ben Voigt,

16

Preferisco non pensare in termini assoluti (come "ultima risorsa") ma relativamente al dominio del problema.

Il C ++ può offrire diversi modi per gestire la vita. Alcuni di loro provano a ricondurre gli oggetti in modo guidato dallo stack. Alcuni altri tentano di sfuggire a questa limitazione. Alcuni sono "letterali", altri sono approssimazioni.

In realtà puoi:

  1. usa la semantica di valore puro . Funziona per oggetti relativamente piccoli in cui ciò che è importante sono i "valori" e non le "identità", in cui si può presumere che due persone Personuguali namesiano la stessa persona (meglio: due rappresentazioni di una stessa persona ). La durata è garantita dallo stack della macchina, fine -essenziale- deos non importa al programma (dal momento che una persona è il suo nome , non importa cosa lo Personsta portando)
  2. usa oggetti allocati in pila e riferimenti o puntatori correlati: consente il polimorfismo e garantisce la durata dell'oggetto. Non sono necessari "puntatori intelligenti", poiché si garantisce che nessun oggetto possa essere "puntato" da strutture che lasciano nello stack più a lungo dell'oggetto a cui puntano (creare prima l'oggetto, quindi le strutture che lo fanno riferimento).
  3. usa gli oggetti allocati heap gestiti dallo stack : questo è ciò che fa std :: vector e tutti i contenitori, e lo std::unique_ptrfa wat (puoi pensarlo come un vettore con dimensione 1). Ancora una volta, ammetti che l'oggetto inizia a esistere (e termina la sua esistenza) prima (dopo) della struttura di dati a cui si riferiscono.

Il punto debole di questo mehtod è che i tipi e le quantità di oggetti non possono variare durante l'esecuzione di chiamate a livello di stack più profonde rispetto a dove vengono create. Tutte queste tecniche "falliscono" la loro forza in tutte le situazioni in cui la creazione e la cancellazione di oggetti sono conseguenza delle attività dell'utente, in modo che il tipo di runtime dell'oggetto non sia noto in fase di compilazione e possano esserci sovrastrutture che si riferiscono ad oggetti l'utente chiede di rimuovere da una chiamata di funzione a livello di stack più profonda. In questi casi, devi:

  • introdurre una disciplina sulla gestione degli oggetti e delle relative strutture di riferimento o ...
  • andare in qualche modo al lato oscuro di "sfuggire alla vita pura basata sullo stack": l'oggetto deve lasciare indipendentemente dalle funzioni che li hanno creati. E deve partire ... fino a quando non saranno necessari .

C ++ isteslf non ha alcun meccanismo nativo per monitorare quell'evento ( while(are_they_needed)), quindi devi approssimarti con:

  1. usa la proprietà condivisa : la vita degli oggetti è vincolata a un "contatore di riferimento": funziona se la "proprietà" può essere organizzata gerarchicamente, fallisce dove possono esistere anelli di proprietà. Questo è ciò che fa std :: shared_ptr. E weak_ptr può essere utilizzato per interrompere il ciclo. Funziona la maggior parte delle volte ma fallisce nel design di grandi dimensioni, dove molti designer lavorano in team diversi e non c'è una ragione chiara (qualcosa che proviene da un requisito un po ') su chi ammuffisce possedere cosa (l'esempio tipico sono le catene a doppio gradimento: è il precedente a causa del prossimo riferimento al precedente o successivo possesso del precedente riferimento al successivo? In seguito a un requisito le soluzioni sono equivalenti e in grandi progetti si rischia di confonderle)
  2. Usa un mucchio di immondizia : semplicemente non ti importa della vita. Corri il collezionista di volta in volta e ciò che è irraggiungibile è considerato "non più necessario" e ... beh ... ahem ... distrutto? finalizzato? congelato?. Esistono numerosi raccoglitori di GC, ma non ne trovo mai uno che sia veramente consapevole del C ++. La maggior parte di essi libera la memoria, senza preoccuparsi della distruzione degli oggetti.
  3. Utilizzare un Garbage Collector compatibile con C ++ , con un'interfaccia di metodi standard adeguata. Buona fortuna per trovarlo.

Passando alla prima soluzione all'ultima, la quantità di struttura di dati ausiliari richiesta per gestire la durata degli oggetti aumenta, man mano che il tempo impiegato per organizzarlo e mantenerlo.

Garbage Collector ha un costo, shared_ptr ne ha di meno, unique_ptr anche di meno e gli oggetti gestiti dallo stack ne hanno pochissimi.

L shared_ptr'"ultima risorsa"? No, non lo è: l'ultima risorsa sono i netturbini. shared_ptrè in realtà l' std::ultima risorsa proposta. Ma potrebbe essere la giusta soluzione, se ti trovi nella situazione che ho spiegato.


9

L'unica cosa menzionata da Herb Sutter in una sessione successiva è che ogni volta che copi un, shared_ptr<>c'è un incremento / decremento interbloccato che deve accadere. Su codice multi-thread su un sistema multi-core, la sincronizzazione della memoria non è insignificante. Data la scelta, è meglio usare un valore di stack oppure a unique_ptr<>e passare riferimenti o puntatori non elaborati.


1
Oppure passa shared_ptrper lvalue o rvalue reference ...
ronag

8
Il punto è che non basta usare shared_ptrcome se fosse il proiettile d'argento che risolverà tutti i problemi di perdita di memoria solo perché è nello standard. È una trappola allettante, ma è comunque importante essere consapevoli della proprietà delle risorse e, a meno che quella proprietà non sia condivisa, a shared_ptr<>non è l'opzione migliore.
Eclipse,

Per me questo è il dettaglio meno importante. Vedi ottimizzazione prematura. Nella maggior parte dei casi ciò non dovrebbe guidare la decisione.
Guy Sirton

1
@gbjbaanb: sì, sono a livello di CPU, ma su un sistema multi-core stai invalidando le cache e forzando le barriere di memoria.
Eclipse,

4
In un progetto di gioco su cui ho lavorato, abbiamo scoperto che la differenza di prestazioni era molto significativa, al punto in cui avevamo bisogno di 2 diversi tipi di puntatore conteggio di riferimento, uno che era thread-safe, uno che non lo era.
Kylotan,

7

Non ricordo se l'ultimo "ricorso" fosse la parola esatta che usava, ma credo che il vero significato di ciò che disse fosse l'ultima "scelta": date chiare condizioni di proprietà; unique_ptr, weak_ptr, shared_ptr e persino i puntatori nudi hanno il loro posto.

Una cosa su cui erano tutti d'accordo è che siamo (sviluppatori, autori di libri, ecc.) Tutti nella "fase di apprendimento" di C ++ 11 e che vengono definiti modelli e stili.

Ad esempio, Herb ha spiegato che dovremmo aspettarci nuove edizioni di alcuni dei libri fondamentali sul C ++, come Effective C ++ (Meyers) e C ++ Coding Standards (Sutter & Alexandrescu), tra un paio di anni mentre l'esperienza del settore e le migliori pratiche con C ++ 11 panoramica.


5

Penso che quello che sta ottenendo è che sta diventando comune per tutti scrivere share_ptr ogni volta che potrebbero aver scritto un puntatore standard (come una sorta di sostituzione globale), e che viene utilizzato come cop-out invece di progettare o almeno pianificazione per la creazione e la cancellazione di oggetti.

L'altra cosa che la gente dimentica (oltre al collo di bottiglia di blocco / aggiornamento / sblocco menzionato nel materiale sopra), è che shared_ptr da solo non risolve i problemi del ciclo. Puoi ancora perdere risorse con shared_ptr:

L'oggetto A, contiene un puntatore condiviso a un altro oggetto A L'oggetto B crea A a1 e A a2 e assegna a1.otherA = a2; e a2.otherA = a1; Ora, i puntatori condivisi dell'oggetto B che ha usato per creare a1, a2 escono dall'ambito (diciamo alla fine di una funzione). Ora hai una perdita: nessun altro si riferisce a a1 e a2, ma si riferiscono l'un l'altro, quindi i loro conteggi di riferimento sono sempre 1 e hai fatto trapelare.

Questo è il semplice esempio, quando questo si verifica nel codice reale, di solito accade in modi complicati. Esiste una soluzione con weak_ptr, ma così tante persone ora condividono ovunque_ptr dappertutto e non conoscono nemmeno il problema della perdita o persino di weak_ptr.

Per concludere: penso che i commenti a cui fa riferimento il PO si riducano a questo:

Indipendentemente dal linguaggio in cui stai lavorando (gestito, non gestito o in mezzo a conteggi di riferimento come shared_ptr), devi comprendere e decidere intenzionalmente la creazione, la durata e la distruzione degli oggetti.

modifica: anche se ciò significa "sconosciuto, devo usare shared_ptr", ci hai ancora pensato e lo stai facendo intenzionalmente.


3

Risponderò dalla mia esperienza con Objective-C, un linguaggio in cui tutti gli oggetti vengono contati e allocati nell'heap. A causa del fatto di avere un modo di trattare gli oggetti, le cose sono molto più facili per il programmatore. Ciò ha permesso di definire regole standard che, una volta rispettate, garantiscono la solidità del codice e nessuna perdita di memoria. Ha anche reso possibile l'ottimizzazione intelligente delle ottimizzazioni del compilatore come il recente ARC (conteggio automatico dei riferimenti).

Il mio punto è che shared_ptr dovrebbe essere la tua prima opzione piuttosto che l'ultima risorsa. Utilizza il conteggio dei riferimenti per impostazione predefinita e altre opzioni solo se sei sicuro di ciò che stai facendo. Sarai più produttivo e il tuo codice sarà più robusto.


1

Proverò a rispondere alla domanda:

Come possiamo programmare senza std :: shared_ptr e gestire comunque la durata degli oggetti in modo sicuro?

C ++ ha un gran numero di modi diversi per fare memoria, ad esempio:

  1. Utilizzare struct A { MyStruct s1,s2; };invece di shared_ptr nell'ambito della classe. Questo è solo per programmatori esperti perché richiede di capire come funzionano le dipendenze e richiede la capacità di controllare le dipendenze abbastanza da limitarle a un albero. L'ordine delle classi nel file di intestazione è un aspetto importante di questo. Sembra che questo utilizzo sia già comune con i tipi c ++ nativi incorporati, ma l'utilizzo con le classi definite dal programmatore sembra essere meno utilizzato a causa di questi problemi di dipendenza e ordine delle classi. Questa soluzione ha anche problemi con sizeof. I programmatori vedono i problemi in questo come un requisito per usare dichiarazioni forward o #includes inutili e quindi molti programmatori torneranno alla soluzione inferiore di puntatori e successivamente a shared_ptr.
  2. Usa MyClass &find_obj(int i);+ clone () invece di shared_ptr<MyClass> create_obj(int i);. Molti programmatori vogliono creare fabbriche per creare nuovi oggetti. shared_ptr è ideale per questo tipo di utilizzo. Il problema è che presuppone già una soluzione di gestione della memoria complessa utilizzando l'allocazione dell'heap / archivio gratuito, anziché una soluzione stack o basata su oggetti più semplice. Una buona gerarchia di classi C ++ supporta tutti gli schemi di gestione della memoria, non solo uno di essi. La soluzione basata su riferimenti può funzionare se l'oggetto restituito è archiviato all'interno dell'oggetto contenitore, anziché utilizzare la variabile di ambito della funzione locale. Il passaggio di proprietà dalla fabbrica al codice utente dovrebbe essere evitato. Copiare l'oggetto dopo aver usato find_obj () è un buon modo per gestirlo: i normali costruttori di copie e il normale costruttore (di diversa classe) con parametro di riferimento o clone () per gli oggetti polimorfici possono gestirlo.
  3. Uso di riferimenti anziché puntatori o shared_ptrs. Ogni classe c ++ ha costruttori e ogni membro dei dati di riferimento deve essere inizializzato. Questo utilizzo può evitare molti usi di puntatori e shared_ptrs. Devi solo scegliere se la tua memoria è all'interno dell'oggetto o al di fuori di esso e scegliere la soluzione struct o la soluzione di riferimento in base alla decisione. I problemi con questa soluzione sono in genere correlati all'evitare i parametri del costruttore, che è pratica comune ma problematica e l'incomprensione del modo in cui le interfacce per le classi dovrebbero essere progettate.

"Il passaggio di proprietà dalla fabbrica al codice utente dovrebbe essere evitato." E cosa succede quando ciò non è possibile? "Uso di riferimenti anziché puntatori o shared_ptrs." Uhm, no. I puntatori possono essere ripristinati. I riferimenti non possono. Ciò impone restrizioni sui tempi di costruzione su ciò che è memorizzato in una classe. Questo non è pratico per molte cose. La tua soluzione sembra essere molto rigida e non flessibile alle esigenze di un'interfaccia e di un modello di utilizzo più fluidi.
Nicol Bolas,

@Nicol Bolas: una volta che segui le regole sopra, gli ref verranno usati per le dipendenze tra gli oggetti e non per l'archiviazione dei dati come hai suggerito. Le dipendenze sono più stabili dei dati, quindi non entriamo mai nel problema che stavi considerando.
tp1

Ecco un esempio molto semplice. Hai un'entità di gioco, che è un oggetto. Deve fare riferimento a un altro oggetto, che è un'entità bersaglio con cui deve parlare. Tuttavia, gli obiettivi possono cambiare. I bersagli possono morire in vari punti. E l'entità deve essere in grado di gestire queste circostanze. Il tuo approccio rigido senza puntatori non può gestire nemmeno qualcosa di semplice come cambiare bersaglio, per non parlare della morte del bersaglio.
Nicol Bolas,

@nicol bolas: oh, che viene gestito in modo diverso; l'interfaccia della classe supporta più di una "entità". Invece di mappare 1: 1 tra oggetti ed entità, utilizzerai entityarray. Quindi le entità muoiono molto facilmente rimuovendole dall'array. C'è solo un piccolo numero di entità in tutto il gioco e le dipendenze tra le matrici non cambiano molto spesso :)
tp1

2
No, unique_ptrè più adatto alle fabbriche. Puoi trasformare a unique_ptrin a shared_ptr, ma è logicamente impossibile andare nella direzione opposta.
Ben Voigt,
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.