Contenitore modello polimorfico: shared_ptr vs reference_wrapper

5

Supponendo che abbiamo due classi:

class A
{
    ...
}
class B : public A
{
    ...
}

Sarebbe meglio scrivere

std::deque<shared_ptr<A> > container;

o

std::deque<reference_wrapper<A> > container;

per creare un contenitore che sia in grado di contenere riferimenti a entrambe queste classi e perché?

Nel mio caso, la classe che contiene il contenitore non gli interesserebbe, se un riferimento in qualche modo diventasse non valido.

Tuttavia ci sono dei caveat da tenere in considerazione quando si sceglie un approccio rispetto all'altro?

    
posta Benjamin Rickels 17.02.2015 - 01:00
fonte

3 risposte

5

Ecco la mia opinione in merito:

  1. considera se entrambe le varianti possono essere effettivamente utilizzate nel tuo caso. reference_wrapper è, in base alla progettazione, non costruibile in base all'impostazione predefinita. Ciò significa che non potrai, ad esempio, chiamare container.resize() quando utilizzi il wrapper di riferimento. Un shared_ptr , d'altra parte, è costruito in modo predefinito in uno stato NULL / non valido. Pertanto, per alcuni casi di utilizzo, non puoi utilizzare reference_wrapper . Per dare alcuni esempi:

    int i = 42;
    std::reference_wrapper<int> iref(i);             // OK
    std::reference_wrapper<int> uninitialized_iref;  // Not OK, but OK with shared_ptr
    
    iref = 23;                     // Not OK, but OK on a "normal" reference
    int& iref2 = iref; iref2 = 32; // OK
    static_cast<int&>(iref) = 23;  // OK
    iref.get() = 23;               // OK
    
    std::vector<std::reference_wrapper<int> > irefs; // OK
    irefs.resize(10);    // Not OK!
    irefs.resize(10, i); // OK
    
  2. Gli esempi sopra suggeriscono già qualcos'altro: usabilità e leggibilità. A seconda del tuo caso d'uso, devi decidere se preferisci la notazione in stile puntatore ( shared_ptr ) o la notazione in stile più-o-meno-riferimento ( reference_wrapper ) e se ciò rende il codice più mantenibile. Personalmente apprezzo molto questo aspetto.

  3. Come sottolineato in un commento alla tua domanda, anche la durata dell'oggetto svolge un ruolo. Quando la durata del contenitore è destinata a superare la durata di uno degli oggetti di riferimento, hai per usare shared_ptr (o magari trovare un design di sistema diverso). Con i riferimenti è necessario essere certi dei rispettivi ambiti. Sospetto anche che nel tuo caso specifico, dovrebbe preoccuparsi veramente dei riferimenti che diventano non validi - se ciò può accadere, ti troverai nei guai, e di solito sarebbe difficile eseguire il debug.

  4. La memorizzazione di shared_ptr di elementi implica ovviamente che i valori a cui si desidera fare riferimento siano già memorizzati in puntatori condivisi. Non è possibile creare un puntatore condiviso che faccia riferimento a un elemento creato con altri mezzi. Quindi, se hai bisogno di fare riferimento a elementi esistenti , non condivisi , non puoi usare shared_ptr e dovresti andare con reference_wrapper o semplici vecchi puntatori.

  5. Per quanto riguarda le prestazioni, non mi aspetto grandi differenze. reference_wrapper di solito utilizza internamente un puntatore e l'overhead di shared_ptr è generalmente trascurabile.

Tutto sommato, vorrei basare la mia decisione su (a) il mio caso di utilizzo concreto e (b) usabilità / manutenibilità / leggibilità del codice utilizzando il contenitore.

    
risposta data 18.08.2015 - 18:12
fonte
2

È principalmente una questione di durata e proprietà. Un contenitore che utilizza reference_wrapper non possiede i suoi oggetti o ne gestisce la durata, un contenitore che utilizza shared_ptr .

Se il tuo contenitore non deve possedere i suoi oggetti, usa reference_wrapper .

Se dovrebbe possedere i suoi oggetti, allora prenderei una terza opzione. Boost.Pointer Container include classi di template come boost::ptr_deque memorizza i tipi polimorfici e gestisce la proprietà da sé, senza il sovraccarico di shared_ptr o la sintassi scomoda di dover dereference shared_ptr per ogni elemento.

    
risposta data 19.08.2015 - 03:38
fonte
2

La domanda è di proprietà

Non fai menzione di chi possiede gli oggetti che entrano nel contenitore. Poiché shared_ptr è un'opzione, probabilmente esiste una forma di proprietà condivisa e allocazione dello storage dinamico. Una chiara definizione di chi possiede gli oggetti e di come vengono osservati (cioè chi può osservarli) inquadrerà gran parte dell'implementazione .

In my specific case, the class containing the container actually wouldn't care if a reference somehow became invalid.

Il contenitore sta giocando un ruolo di osservatore.

However there might be a more generalised answer and a 'rule of thumb', when one should use one approach over the other.

Data la semantica del reference_wrapper c'è una ragionevole aspettativa che l'oggetto a cui ci si riferisce sia valido mentre reference_wrapper è valido. Nel tuo caso questo potrebbe non essere applicabile, ma in generale questo è vero e qualsiasi manutenzione fatta sul codice in una data successiva potrebbe benissimo assumere questo.

Regola generale

Permetti che la durata degli oggetti di proprietà condivisa sia gestita con std::shared_ptr e utilizzi "observer" std::weak_ptr nel contenitore.

Se la durata degli oggetti non è condivisa e in qualche modo legata alla durata del contenitore stesso (tramite la durata di archiviazione automatica), allora reference_wrapper è vitale (in modo simile potrebbe anche essere un puntatore raw).

    
risposta data 19.08.2015 - 09:36
fonte

Leggi altre domande sui tag