18

Credo che l'espressione T() crei un valore rvalue (secondo lo standard). Tuttavia, il seguente codice compila (almeno gcc4.0):Perché è consentito T() = T()?

class T {}; 

int main() 
{ 
    T() = T(); 
} 

so tecnicamente questo è possibile perché funzioni membro possono essere richiamate sul provvisori ed Quanto sopra è solo richiamando l'operatore = sul rvalue temporanea creata dal primo T().

Ma concettualmente è come assegnare un nuovo valore a un valore. C'è una buona ragione per cui questo è permesso?

Modifica: Il motivo per cui trovo questo strano è che è severamente vietato sui tipi predefiniti, ma è consentito sui tipi definiti dall'utente. Ad esempio, int(2) = int(3) non verrà compilato poiché si tratta di un "valore lvalue non valido nell'assegnazione".

Quindi immagino che la vera domanda sia, è stato questo comportamento un po 'incoerente incorporato nella lingua per un motivo? O è lì per qualche motivo storico? (Ad esempio, sarebbe concettualmente più sano consentire solo alle funzioni membro const di essere invocato su espressioni rvalue, ma ciò non può essere fatto perché ciò potrebbe rompere qualche codice esistente.)

risposta

3

Questo è il motivo per cui molti le classi nella libreria Standard possono essere implementate. Si consideri per esempio std::bitset<>::operator[]

// bit reference: 
class reference { 
    friend class bitset; 
    reference(); 
public: 
    ˜reference(); 
    reference& operator=(bool x);   // for b[i] = x; 
    reference& operator=(const reference&); // for b[i] = b[j]; 
    bool operator˜() const; // flips the bit 
    operator bool() const; // for x = b[i]; 
    reference& flip();  // for b[i].flip(); 
}; 

reference operator[](size_t pos); // for b[i]; 

Se fai bits[i] = true esattamente assegnare un valore a una rvalue di tipo di classe. Il proxy restituito da operator[] può accedere ai bit che sono efficientemente impacchettati in interi.

+0

Questo insieme alla risposta di FredOverflow risponde completamente alla mia domanda. Grazie! – Rimo

13

Ciò è consentito esclusivamente a causa dell'overloading dell'operatore e possibilità che tu possa sovraccaricare il operator = per fare qualcosa di più stravagante, come stampare sulla console, o bloccare un mutex, o qualcosa di veramente.

+16

E non solo operatore = ma anche i costruttori. Ci sono un sacco di strane costruzioni in C++ in cui il compilatore si stringe nelle spalle e spera che tu sappia cosa stai facendo. :-) –

7

Sì, si assegna un nuovo valore a un valore. Più precisamente, si chiama la funzione membro operator = su un valore. Dal momento che non stai utilizzando l'operatore di assegnazione incorporato, perché pensi che questo dovrebbe essere un problema? operator = è una funzione membro della classe, che per molti aspetti è simile a qualsiasi altra funzione membro della classe, incluso il fatto che può essere richiamata su rvalue.

Probabilmente dovresti anche prendere in considerazione il fatto che "essere un valore r" è una proprietà di un'espressione, non una proprietà di un oggetto. È vero che l'espressione T() restituisce un valore. Tuttavia, l'oggetto temporaneo che l'espressione T() produce è ancora un oggetto, a cui è possibile accedere anche come valore di lvalue . Ad esempio, qualche altra funzione membro può essere chiamata sul risultato della cessione, e vedrà il valore "nuova" (appena assegnato) dell'oggetto temporaneo attraverso *this Ivalue

(T() = T()).some_member_function(); 

È anche possibile estendere la durata del temporaneo allegando un riferimento const a esso const T& r = T() = T(); e il valore visualizzato tramite r sarà il "nuovo" valore dell'oggetto. Come Johannes ha giustamente osservato nel suo commento, questo non lo collegherà ad un temporaneo.

+0

Sì, penso di capire il meccanismo di "come" funzionano tali operazioni. Ma sono ancora curioso di sapere perché i progettisti linguistici abbiano permesso che i valori r (o i temporanei creati da espressioni di valore r) fossero mutati in quanto tali? È una svista dalla loro parte o è stata permessa intenzionalmente (Forse c'erano alcuni motivi pratici che erano abbastanza convincenti da giustificare un comportamento apparentemente incoerente tra tipi built-in e tipi definiti dall'utente?) – Rimo

+0

Giusto per chiarire, personalmente vorrei trovarlo molto meno sorprendente se solo le funzioni membro const fossero invocate su espressioni rvalue. – Rimo

+3

@Rimo: Che sconfigge costrutti ragionevoli come 'Atomic (std :: cout) << 1 <<" output ininterrotto "<< std :: endl;' – MSalters

4

Da un POV, è incoerente, ma sei affacciano come è coerente: 1) int e altri tipi predefiniti ancora si comportano come fanno in C, 2) = operatore sulla classe-tipi si comporta come qualsiasi altro metodo fa senza richiedere un altro caso speciale.

La compatibilità C è stata molto apprezzata dall'inizio del C++, e C++ probabilmente non sarebbe qui oggi senza di esso. Quindi quella parte è generalmente una buona cosa.

Il secondo punto è sottovalutato. Operatore di casing non speciale = consente al codice nonsense di "funzionare", ma perché ci preoccupiamo del codice nonsense in primo luogo? Immondizia, spazzatura.Le regole attuali danno un significato definito (UB qui sarebbe male) con costi trascurabili, per quanto ho mai visto.

Dati i miei druthers, le cose sarebbero state semplificate ulteriormente, quindi int() = int() sarebbe consentito. C++ 0x inizia a dirigersi in quella direzione con rvalue riferimenti, prvalues, ecc

+0

"_C++ 0x inizia a dirigersi in quella direzione con riferimenti di rvalore, valori predefiniti, ecc." Hug? – curiousguy

5

È possibile limitare l'operatore = per lavorare solo su lvalue a C++ 0x:

class T 
{ 
public: 
    T& operator=(const T&) & = default; 
}; 
Problemi correlati