2013-07-08 18 views
22

Quali sono i casi di utilizzo più comuni di "riferimenti rvalue per * questo" che lo standard chiama anche qualificatori di riferimento per le funzioni membro?Quali sono i "riferimenti rvalue per * questo" per?

A proposito, c'è una spiegazione molto buona su questa funzione lingua here.

+0

Con i compilatori che lo supportano ora, mi aspetto di vedere presto un articolo o due sull'argomento. – chris

+0

gcc l'ha appena implementato in 4.8.1, quindi non molte persone hanno ancora usato questa funzione. –

+0

Fondamentalmente ogni volta che si desidera che gli utenti non creino oggetti permanenti di una classe, ma usino solo i provvisori, è possibile ottenere tale risultato rendendo le funzioni membro desiderate '&&'. –

risposta

19

Quando chiamato, ogni funzione membro ha un parametro oggetto implicito che fa riferimento a *this.

Quindi (a) queste normali sovraccarichi di funzioni:

void f(const T&); 
void f(T&&); 

quando viene chiamato come f(x); e (b) questi funzione membro sovraccarica:

struct C 
{ 
    void f() const &; 
    void f() &&; 
}; 

quando viene chiamato come x.f() - sia (a) e (b) la spedizione con la vitalità simile e la classifica.

Quindi i casi d'uso sono essenzialmente gli stessi. Devono supportare lo spostamento dell'ottimizzazione semantica. Nella funzione di membro rvalue potete essenzialmente saccheggiare le risorse oggetti perché si sa che è un oggetto in scadenza (sta per essere cancellato):

int main() 
{ 
    C c; 
    c.f(); // lvalue, so calls lvalue-reference member f 
    C().f(); // temporary is prvalue, so called rvalue-reference member f 
    move(c).f(); // move changes c to xvalue, so again calls rvalue-reference member f 
} 

Così, per esempio:

struct C 
{ 
    C operator+(const C& that) const & 
    { 
     C c(*this); // take a copy of this 
     c += that; 
     return c; 
    } 

    C operator+(const C& that) && 
    { 
     (*this) += that; 
     return move(*this); // moving this is ok here 
    } 
} 
+0

Considerate anche i membri che non hanno senso chiamare su rvalues, come l'operatore di assegnazione. – Puppy

+0

@DeadMG: Sì , un valore di rvalue (valore di valore o valore di x) non si legherà in modo vitale a 'void f (T &)', quindi i casi d'uso sono gli stessi di 'T :: f() e 'il parametro dell'oggetto implicito. si noti la relazione ortogonale tra un parametro di funzione normale e il parametro oggetto implicito - quindi una comprensione generale delle categorie di valori e della selezione di sovraccarico quando si applicano a un parametro di funzione normale può essere ridistribuita così com'è al parametro dell'oggetto implicito della funzione membro (con solo minore differenze non degne di essere menzionate) –

+0

FWIW, non mi preoccuperei nemmeno di spostarmi, basta aggiungere a se stesso e spostarsi in seguito. –

5

Alcune operazioni possono essere più efficiente quando viene chiamato su rvalue, quindi sovraccaricare la categoria di valore di *this consente di utilizzare automaticamente l'implementazione più efficiente, ad es.

struct Buffer 
{ 
    std::string m_data; 
public: 
    std::string str() const& { return m_data; }  // copies data 
    std::string str()&& { return std::move(m_data); } // moves data 
}; 

(Questa ottimizzazione potrebbe essere fatto per std::ostringstream, ma non è stato formalmente proposto per quanto ne so.)

Alcune operazioni non hanno senso per chiamare il rvalues, così sovraccarico sul *this permette al modulo rvalue da eliminare:

struct Foo 
{ 
    void mutate()&; 
    void mutate()&& = delete; 
}; 

non ho effettivamente bisogno di utilizzare questa funzione ancora, ma forse troverete più usi per ora che i due compilatori mi preoccupano sostegno esso.

+0

"Per le funzioni membro non statiche dichiarate senza qualificatore ref, si applica una regola aggiuntiva: anche se il parametro oggetto implicito non è const-qualificato, un valore rvalue può essere associato al parametro come lungo come in tutti gli altri aspetti l'argomento può essere convertito nel tipo del parametro oggetto implicito. " Quindi 'Foo :: mutate()' con nessun qualificatore ref è possibile contro rvalue questo .... –

+0

In aggiunta alla classifica: "S1 e S2 sono riferimenti di riferimento (8.5.3) e nessuno dei due si riferisce a un parametro oggetto implicito di a Funzione membro non statico __dichiarata senza qualificatore di riferimento__, e S1 associa un riferimento di rvalore a un valore di rvalore e S2 associa un riferimento di lvalue. ". Quindi usare un valore di rvalore sarà ambiguo tra 'mutate() no-ref-qualifier' e' mutate() && '. Era questa la tua intenzione? –

+0

cioè penso che si finisce con l'effetto desiderato, ma la diagnostica può essere non ideale (errore ambiguo sovraccarico). –

1

Nel mio framework del compilatore (che verrà rilasciato Sometime Soon ™), si passano voci di informazioni come token in un oggetto del compilatore, quindi si chiama finalize per indicare la fine del flusso.

Sarebbe male distruggere un oggetto senza chiamare finalize, perché non svuoterebbe tutto il suo output. Tuttavia, il distruttore non può eseguire finalize, perché può generare un'eccezione e allo stesso modo è errato chiedere finalize per altri output se il parser è già in fase di interruzione.

Nel caso in cui tutti gli input siano già incapsulati da un altro oggetto, è piacevole passare l'input a un oggetto del compilatore rvalue.

pile< lexer, parser >(output_handler).pass(open_file("source.q")); 

Senza un sostegno speciale, questo deve essere corretto perché finalize non è sempre chiamato. L'interfaccia non dovrebbe permettere all'utente di fare una cosa del genere.

La prima cosa da fare è escludere il caso in cui finalize non viene mai chiamato. L'esempio precedente è annullato se il prototipo è regolata con un lvalue ref-qualificazione come questo:

void pass(input_file f) & { 
    process_the_file(); 
} 

Questo rende spazio per aggiungere un altro sovraccarico che finalizza correttamente l'oggetto. È abilitato per il rinnovo del valore, quindi viene selezionato solo se chiamato su un oggetto che sta scadendo.

void pass(input_file f) && { 
    pass(std::move(f)); // dispatch to lvalue case 
    finalize(); 
} 

Ora l'utente quasi mai bisogno di preoccuparsi di ricordarsi di chiamare finalize, poiché la maggior parte degli oggetti del compilatore sono in ultima analisi istanziati come provvisori.


Nota, questo genere di cose non è specifico per i membri qualificati. Qualsiasi funzione può avere sovraccarichi separati per t & e t &&. Il modo in cui pass è in realtà attualmente implementata utilizza l'inoltro perfetto e poi fa marcia indietro per determinare la semantica corretta:

template< typename compiler, typename arg > 
void pass(compiler && c, arg a) { 
    c.take_input(a); 

    if (! std::is_reference<compiler>::value) { 
     c.finalize(); 
    } 
} 

Ci sono molti modi per affrontare il sovraccarico. In realtà, le funzioni dei membri non qualificate sono inusuali in non e si preoccupano della categoria (lvalue o rvalue) dell'oggetto su cui sono chiamate e non passano tali informazioni nella funzione. Qualsiasi parametro di funzione oltre l'implicito this deve dire qualcosa sulla categoria del suo argomento.

Problemi correlati