C ++ Gof Design Patterns dipende molto da new o shared_ptr

3

Sto cercando di imparare le migliori pratiche per la progettazione del codice e il riutilizzo in C ++, quindi sto passando attraverso i ben noti elementi GoF D esign Patterns del software orientato agli oggetti riutilizzabile.

Ho notato che quasi tutti i modelli di progettazione utilizzano oggetti allocati dinamicamente. Per evitare perdite di memoria, mi sto orientando verso l'utilizzo di shared_ptr per tutte queste classi. Fondamentalmente, shared_ptr di solito punta a un'interfaccia di classe astratta e questo è il modo in cui gli oggetti interagiscono tra loro.

Quando cerco la discussione su shared_ptrs per C ++, la gente dice che se tu usi shared_ptrs ovunque nel tuo codice C ++, probabilmente stai facendo cose sbagliate . Queste persone sanno di cosa stanno parlando o dovrei fermamente mantenere l'allocazione dinamica dell'heap per tutti i miei oggetti?

Ecco un esempio che ho trovato dei commentatori che dicono di evitare questa pratica di progettazione: link

Esempio di interfaccia comune

std::shared_ptr<Inferface> item1(new ConcreteClassA());
std::shared_ptr<Inferface> item2(new ConcreteClassB()); 
item1->Action();
item2->Action();

Esempio di evitare interfacce comuni e allocazione heap

void Action(ConcreteClassA item){
     item.Action();
}
void Action(ConcreteClassB item){
     item.Action();
}

ConcreteClassA item1;
ConcreteClassB item2;
Action(item1);
Action(item2);

Il primo esempio è chiaramente superiore per il riutilizzo del codice poiché non devo scrivere nuove funzioni per ogni nuova classe, ma ciò implica shared_ptrs e allocazione dell'heap. Questo è un esempio molto semplice del perché i pattern di progettazione sono utili e ovviamente è molto più complicato di così.

Sono in un punto di confusione su quale approccio è considerato una pratica comune per la progettazione di software con C ++. Diciamo che sto facendo software applicativo che avrò bisogno di mantenere in più anni, come un esempio di editor di testo WYSIWYG dal libro GoF. Quando la gente dice che non si dovrebbe usare ampiamente shared_ptr per gestire gli oggetti vuol dire che dovrei usare altri puntatori intelligenti / puntatori grezzi o è la pratica comune del C ++ per evitare del tutto l'allocazione dinamica dell'heap?

Per me, OOP ha più senso per un linguaggio come Java, dove la garbage collection è automatica e le interfacce fanno parte del linguaggio.

La pratica del settore è di seguire schemi di progettazione che utilizzano l'allocazione diana durante lo sviluppo di applicazioni in C ++? Se è così, i puntatori intelligenti sono la giusta strada da percorrere?

    
posta Christian Gabor 08.11.2018 - 23:13
fonte

2 risposte

9

I modelli di progettazione GoF sono metodi intelligenti per utilizzare il polimorfismo per mantenere un design estendibile. Ad esempio, il modello di strategia ci consente di fornire diverse implementazioni di strategia senza dover ricompilare alcun codice che utilizza la strategia.

Le tecniche orientate agli oggetti (polimorfismo / dispacciamento dinamico) significano che quando abbiamo un oggetto, non dobbiamo sapere che è il tipo effettivo. Dovrebbe essere sufficiente conoscere il suo Interface , non se sia ConcreteClassA o ConcreteClassB . Non stai utilizzando la spedizione dinamica quando il tipo di un oggetto è completamente noto, ad es. quando l'oggetto è memorizzato dal valore in una variabile locale. Quando si utilizza una funzione sovraccaricata, questa viene risolta in fase di compilazione tramite invio statico e non ha le proprietà di estensibilità dell'invio dinamico. Per maggiori dettagli, leggi il mio articolo Invio dinamico rispetto a quello statico .

Quando conosciamo solo l'interfaccia di un oggetto e non il suo tipo dinamico, è necessario tenere l'oggetto tramite un puntatore. Questo potrebbe essere un puntatore, un riferimento o un puntatore intelligente. Questi diversi tipi di puntatori implicano una semantica della proprietà diversa.

  • I puntatori grezzi non implicano alcuna proprietà e dovrebbero quindi essere evitati. Devo cancellare questo o l'oggetto è appena preso in prestito? Poco chiaro. I puntatori grezzi sono una ricetta per perdite di memoria e segfaults. È difficile scrivere codice eccezionalmente sicuro con i puntatori grezzi.
  • I riferimenti significano che l'oggetto è temporaneamente preso in prestito ed è di proprietà di qualche altro oggetto. Pratico: un riferimento non può mai essere un puntatore nullo e non deve essere esplicitato in modo esplicito.
  • Un std::unique_ptr trasferisce la proprietà dell'oggetto a cui è stato fatto riferimento. Una volta che il puntatore intelligente viene distrutto automaticamente, anche l'oggetto puntato verrà eliminato (→ RAII). A meno che un riferimento o un puntatore condiviso siano più appropriati, questo dovrebbe essere il tipo di puntatore predefinito.
  • Un std::shared_ptr indica la proprietà condivisa. L'oggetto puntato viene automaticamente conteggiato come riferimento. Una volta che l'ultimo puntatore condiviso che fa riferimento a quell'oggetto viene distrutto, viene eliminato anche l'oggetto puntato. Il conteggio dei riferimenti implica un sovraccarico di runtime.

Pertanto, il consiglio per evitare i puntatori condivisi è corretto. Molti oggetti grafici hanno una chiara proprietà e non hanno bisogno di un approccio di raccolta dei rifiuti come il conteggio dei riferimenti. Eppure, se ne hai bisogno, l'opzione è ancora lì. Nota che i puntatori condivisi non sono proprio come la garbage collection in stile Java, perché devi comunque evitare i cicli di riferimento (ad esempio usando i puntatori deboli).

I pattern GoF considerano solo le tecniche orientate agli oggetti, tuttavia il C ++ è molto più di questo. In particolare, i modelli possono talvolta risolvere problemi simili. Ma i modelli e le OOP sono fondamentalmente diversi. È importante sottolineare che la modifica di un modello richiede la ricompilazione di tutto il codice dipendente, mentre le tecniche OOP possono isolare i componenti.

    
risposta data 09.11.2018 - 00:22
fonte
2

Una "regola" migliore è "No naked new"; si dovrebbe usare il puntatore intelligente appropriato dove applicabile:

std :: unique_ptr

usato per allocazioni univoche che non saranno condivise se non da punti deboli. Un puntatore univoco ha un singolo proprietario

std :: shared_ptr

utilizzato per allocazioni condivise che non hanno un proprietario

std :: weak_ptr

utilizzato per riferimenti non di proprietà

Molti usi di modelli di progettazione possono essere implementati con unique_ptr che restituisce riferimenti a weak_ptr. Utilizza solo un puntatore condiviso per allocazioni che non hanno un proprietario o sopravviveranno al loro creatore.

    
risposta data 09.11.2018 - 00:20
fonte

Leggi altre domande sui tag