Lavorando intorno al C ++ limitazione sui riferimenti non const a provvisori
-
03-10-2019 - |
Domanda
Ho un C ++ data-struttura che è un "scartafaccio" richiesto per altri calcoli. Non è a lungo vissuto, e non è spesso utilizzato in modo non prestazioni critiche. Tuttavia, comprende un generatore di numeri casuali tra gli altri campi di rilevamento aggiornabili, e mentre il valore effettivo del generatore non è importante, è importante che il valore viene aggiornato anziché copiato e riutilizzato. Questo significa che, in generale, oggetti di questa classe sono passati per riferimento.
Se un'istanza è necessaria solo una volta, l'approccio più naturale è di costruire loro dovunque necessario (magari utilizzando un metodo di fabbrica o di un costruttore), e poi passando la scratchpad al metodo consumare. Metodo di firme consumatori utilizzano passano di riferimento dal momento che non sanno questo è l'unico uso, ma i metodi di fabbrica e costruttori ritorno per valore -. e non è possibile passare provvisori senza nome per riferimento
C'è un modo per evitare di intasare il codice con variabili temporanee brutte? Mi piacerebbe evitare le cose come il seguente:
scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
xyz.initialize_computation(useless_temp);
ho potuto fare la scratchpad intrinsecamente mutable
e basta etichettare tutti i parametri const &
, ma questo non mi sembra delle migliori pratiche dal momento che è fuorviante, e non posso fare questo per le classi I non controllano completamente. Il passaggio per riferimento rvalue richiederebbe l'aggiunta di sovraccarichi a tutti i consumatori di scartafaccio, che tipo di sconfitte lo scopo -. Avere il codice chiaro e conciso
Dato che le prestazioni non è critica (ma le dimensioni del codice e la leggibilità sono), qual è l'approccio best-practice per il passaggio in tale scartafaccio? con C ++ 0x dispone è OK se < em> richiesta ??em> ma preferibilmente C ++ 03-sole caratteristiche dovrebbero essere sufficienti.
Modifica: Per essere chiari, utilizzando una temporanea è fattibile, è solo un peccato disordine nel codice che vorrei evitare. Se non avete mai dare il nome di una temporanea, è chiaramente usato una sola volta, e il minor numero di righe di codice per leggere, meglio è. Inoltre, in inizializzatori costruttori, è impossibile dichiarare provvisori.
Soluzione
Anche se non c'è problema a passare rvalues ??alle funzioni che accettano i riferimenti non-const, è bene chiamare le funzioni membro su rvalues, ma la funzione di membro non sa come si chiamava. Se si restituisce un riferimento all'oggetto corrente, è possibile convertire rvalues ??a lvalue:
class scratchpad_t
{
// ...
public:
scratchpad_t& self()
{
return *this;
}
};
void foo(scratchpad_t& r)
{
}
int main()
{
foo(scratchpad_t().self());
}
Si noti come la chiamata a self()
produce un lvalue anche se scratchpad_t
è un rvalue.
Si prega di correggetemi se sbaglio, ma i parametri di riferimento rvalue non accetta i riferimenti lvalue in modo che li utilizzano richiederebbe l'aggiunta di sovraccarichi a tutti i consumatori di scartafaccio, che è anche un peccato.
Bene, si potrebbe utilizzare i modelli ...
template <typename Scratch> void foo(Scratch&& scratchpad)
{
// ...
}
Se si chiama foo
con un parametro rvalue, Scratch
sarà dedotta al scratchpad_t
, e quindi Scratch&&
sarà scratchpad_t&&
.
E se si chiama foo
con un parametro lvalue, Scratch
sarà dedotta al scratchpad_t&
, ed a causa di regole di riferimento collasso, Scratch&&
sarà anche scratchpad_t&
.
Si noti che il parametro scratchpad
formale è un nome e quindi un valore assegnabile, non importa se il suo tipo è un riferimento lvalue o un riferimento rvalue. Se si desidera passare scratchpad
ad altre funzioni, non è necessario il trucco modello per quelle funzioni più, basta usare un parametro di riferimento lvalue.
A proposito, Ti rendi conto che la scratchpad temporanea coinvolti in xyz.initialize_computation(scratchpad_t(1, 2, 3));
sarà distrutto non appena initialize_computation
è fatto, giusto? Memorizzare il riferimento all'interno dell'oggetto xyz
per l'utente successivamente sarebbe una pessima idea.
self()
non ha bisogno di essere un metodo membro, può essere una funzione templato
Sì, che è anche possibile, anche se avrei rinominarlo per rendere più chiara l'intenzione:
template <typename T>
T& as_lvalue(T&& x)
{
return x;
}
Altri suggerimenti
Il problema è solo che questo:
scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
è brutto? Se è così, allora perché non cambiare per questo:?
auto useless_temp = factory(rng_parm);
Personalmente, avrei preferito vedere const_cast
che mutable
. Quando vedo mutable
, sto supponendo che qualcuno sta facendo logica const
-ness, e non credo che gran parte di essa. const_cast
tuttavia solleva bandiere rosse, come il codice come questo dovrebbe.
Una possibilità potrebbe essere quella di usare qualcosa come shared_ptr
(auto_ptr
avrebbe funzionato anche a seconda di ciò factory
sta facendo) e farlo passare per valore, che evita il costo copia e mantiene solo una singola istanza, ma può essere passato dal tuo fabbrica metodo.
Se si alloca l'oggetto nel mucchio si potrebbe essere in grado di convertire il codice a qualcosa di simile:
std::auto_ptr<scratch_t> create_scratch();
foo( *create_scratch() );
La fabbrica crea e restituisce un auto_ptr
invece di un oggetto nello stack. Il auto_ptr
tornato temporanea avrà la proprietà dell'oggetto, ma si è permesso di chiamare i metodi non-const a titolo temporaneo e si può dereference il puntatore per ottenere un vero e proprio riferimento. Al successivo punto sequenza il puntatore intelligente sarà distrutto e la memoria liberata. Se avete bisogno di passare lo stesso scratch_t
a diverse funzioni di fila si può semplicemente catturare il puntatore intelligente:
std::auto_ptr<scratch_t> s( create_scratch() );
foo( *s );
bar( *s );
Questo può essere sostituito con std::unique_ptr
nello standard imminente.
Ho segnato la risposta di FredOverflow come la risposta per il suo suggerimento di utilizzare un metodo per restituire semplicemente un riferimento non const; Questo funziona in C ++ 03. Tale soluzione richiede un metodo esponente per scratchpad simile tipo, ma in C ++ 0x possiamo anche scrivere che il metodo più in generale per qualsiasi tipo:
template <typename T> T & temp(T && temporary_value) {return temporary_value;}
Questa funzione semplicemente avanti normali riferimenti lvalue e converte rvalue riferimenti in riferimenti lvalue. Certo, fare questo restituisce un valore modificabile il cui risultato viene ignorato -. Che sembra essere esattamente quello che voglio, ma può sembrare strano in alcuni contesti