Come scegliere tra tipi non mobili e non mobili non trasferibili senza fare affidamento sull'intuizione o dettagli di implementazione?

2

Prima dell'introduzione del costruttore di movimento e del trasferimento di incarichi in C ++, avevo due chiare categorie concettuali di classi: valori per i quali l'uso di una copia non era diverso dall'uso del valore originale, entità per le quali era diverso. Per i valori, ho fornito il gestore della copia e l'operatore di assegnazione (e gli operatori di uguaglianza), per le entità che non l'ho fatto (se un'operazione di copia aveva senso, il costruttore di copie era esplicito e l'operazione di copia non era fornita dall'operatore di assegnazione; mai un concetto significativo).

L'introduzione di move ha introdotto una nuova categoria, e purtroppo non sembra aver trovato un criterio concettuale che mi consenta di fare la scelta di fornire operazioni di movimento o no per le classi non di valore. Attualmente, sono principalmente guidato da considerazioni sull'implementazione e questo mi rende insoddisfatto. Inoltre, di recente ho dovuto far evolvere una classe e ho dovuto scegliere se rimuovere le operazioni di spostamento o introdurre una complessità ingiustificata. Fortunatamente, le operazioni di spostamento sono state fornite e non utilizzate e la scelta non è stata quindi così difficile. Questo evento ha evidenziato il fatto che ho avuto un problema concettuale.

Come fare la scelta tra tipi non mobili e non mobili non commutabili senza affidarsi a dettagli di intuizione o implementazione?

    
posta Nicolas Hamblenne 13.08.2016 - 12:36
fonte

3 risposte

3

Il modo di guardare a questo non è una questione di copia contro mossa contro immobilità. Sono due domande separate:

  1. Mobilità vs. immobilità. Vuoi che sia in grado di trasferire il contenuto dell'oggetto su un altro oggetto in qualche modo?

  2. Se è mobile, allora è completamente copiabile o solo spostato?

Il fatto è che la risposta a entrambe le domande non è sempre una questione di design.

mutex non è immobile perché pensiamo concettualmente a mutex come a qualcosa che non può essere spostato. Sappiamo esattamente che cosa dovrebbe significare spostare un mutex : i blocchi su quel mutex funzionano ancora. Non è immobile per design; è immobile per implementazioni . Alcuni backend potrebbero rendere un mutex mobile, ma altri no (senza allocazioni di memoria o altro overhead). Quindi, otteniamo il minimo comune denominatore.

Al contrario, lock_guard è immobile per design. Questo è il suo scopo. Lo stesso vale per tipi come boost::scoped_ptr ; il punto è che la durata del costrutto gestito finirà al termine di tale ambito. Mentre in alcuni momenti potremmo desiderare di spostare tali oggetti, dal punto di vista di coloro che li hanno progettati, non li hanno desiderati per essere mobili.

Quando si tratta della domanda sulla forma di mobilità di un tipo, si applicano le stesse condizioni. unique_ptr è solo movimento perché è il suo lavoro. Ecco perché esiste: per consentire il trasferimento di proprietà uniche da un luogo a un altro.

Al contrario, si potrebbe concepire della capacità di copiare un std::thread . Dovresti semplicemente sospendere l'esecuzione del thread, copiare il suo stack di chiamate e avviare un nuovo thread di esecuzione con lo stack di chiamate copiato. Questo è abbastanza semplice dal punto di vista concettuale, tuttavia è impossibile implementarlo con il modello a oggetti di C ++. Dopotutto, potresti avere un scoped_ptr nello stack, un tipo che non è consentito per copiare. Potresti avere un puntatore / riferimento ad un oggetto stack nello stack ; come lo aggiorni per puntare all'oggetto corretto?

Le implementazioni informano le interfacce. Il problema con i tuoi "criteri concettuali" è che ti fa pensare che puoi costruire un paio di semplici regole per categorizzare il mondo, e se ti attieni a queste categorie, starai bene.

La programmazione non è così semplice. Dovrai imparare come esercitare giudizio ; è una delle abilità di cui un buon programmatore ha bisogno.

    
risposta data 14.08.2016 - 05:39
fonte
1

Hai ragione nel determinare se un tipo debba essere o meno copiato è di solito piuttosto semplice. Ad esempio, non ha senso copiare un oggetto che rappresenta una connessione di rete aperta.

Per quanto riguarda la mobilità, l'importante domanda da porsi è se si prevede che gli utenti mantengano più riferimenti longevi all'oggetto. Infatti, quando qualcosa viene spostato da un oggetto, i contenuti dell'oggetto originale sono ora "vuoti", il che significa in genere che qualsiasi riferimento esistente all'oggetto originale non può più essere utilizzato. Ciò è particolarmente vero quando si considera che una volta spostato un oggetto, l'oggetto originale viene in genere distrutto poco dopo.

Nel caso di std::unique_ptr , la scelta è piuttosto ovvia: non è tipico tenere riferimenti longevi a un oggetto unique_ptr (di solito è il pointee a cui si fa riferimento). Quindi spostare la semantica è accettabile lì - c'è poco rischio di rompere riferimenti o puntatori esistenti.

Nel caso di una connessione di rete aperta, potrebbe avere senso per consentire la semantica del movimento se si prevede che queste connessioni abbiano vita breve e abbiano solo un riferimento ad esse in qualsiasi momento. Questo è un po 'forzato, quindi, in caso di dubbio, non implementerei le operazioni di spostamento per un tale oggetto.

In alcuni casi esiste un chiaro caso contro che supporta la semantica del movimento. Ad esempio, il punto intero di std::mutex deve essere utilizzato da più posizioni contemporaneamente; se la semantica mossa fosse ammessa lì, il comportamento risultante sarebbe molto pericoloso, poiché i blocchi mutex esistenti potrebbero facilmente diventare non validi a causa di una mossa spuria.

    
risposta data 13.08.2016 - 16:38
fonte
0

Avere un costruttore di movimento è solo un'ottimizzazione (molto utile) per le situazioni in cui il linguaggio utilizzato per forzare l'utente a disporre di due oggetti per un breve periodo, quando ne volevi uno solo.

Ad esempio, se la tua funzione restituisce un vettore, la tua funzione costruisce un oggetto vettoriale, quindi l'oggetto vettoriale viene copiato in uno nuovo a cui il chiamante può accedere, quindi spesso quel vettore viene copiato in un altro vettore che il risultato della funzione è assegnato a. Per un breve periodo, devono esistere due oggetti completi, perché la lingua lo ha detto, anche se il programmatore non aveva intenzione di farlo; il secondo oggetto indesiderato verrà eliminato molto presto. I costruttori Move lo rendono molto più efficiente: hai creato un oggetto che volevi creare. Questo è spostato, costruito nel valore di ritorno; in nessun momento hai due oggetti "reali", l'oggetto viene semplicemente spostato in un'altra posizione nella memoria. E lo stesso quando assegnato a una variabile.

Se hai oggetti che possono esistere solo una volta e non possono essere copiati, allora un costruttore di mosse è molto utile. Consente a una funzione di creare e restituire un oggetto di questo tipo. O consente che tali oggetti siano memorizzati in un vettore dove il ridimensionamento del vettore, in caso contrario, necessita di copie da realizzare. (Sono tutte situazioni in cui non volevi davvero copiare un oggetto, succede solo temporaneamente).

    
risposta data 13.08.2016 - 15:26
fonte

Leggi altre domande sui tag