2011-06-10 18 views
23

Ho letto l'efficace terza edizione C++ scritta da Scott Meyers.Perché operator ++ restituisce un valore non const?

L'elemento 3 del libro, "Utilizzare const quando possibile", dice se vogliamo evitare che i rvalues ​​vengano assegnati al valore di ritorno della funzione accidentalmente, il tipo restituito deve essere const.

Ad esempio, la funzione di incremento per iterator:

const iterator iterator::operator++(int) { 
    ... 
} 

Poi, alcuni incidenti è impedito.

iterator it; 

// error in the following, same as primitive pointer 
// I wanted to compare iterators 
if (it++ = iterator()) { 
    ... 
} 

Tuttavia, iteratori, come std::vector::iterator nel GCC non restituiscono const valori.

vector<int> v; 
v.begin()++ = v.begin(); // pass compiler check 

Ci sono dei motivi?

+3

Non si dovrebbe usare comunque ++ su qualsiasi lato di un'espressione di confronto. –

+2

Incredibile quante risposte sbagliate ha avuto. –

+3

Ora è un trucco obsoleto, perché impedisce la costruzione del movimento. Il modo moderno per farlo è definire 'operator =' come predefinito usando un 'e' qualificatore di ref. –

risposta

11

Sono abbastanza sicuro che questo è perché sarebbe svolgere il caos con riferimenti rvalue e ogni sorta di decltype. Anche se queste caratteristiche non erano in C++ 03, si sapeva che sarebbero arrivate.

Ancora più importante, non credo che ogni funzione di restituire il rvalues ​​const, è probabilmente qualcosa che non è stato considerato fino a dopo la norma è stata pubblicata. Inoltre, i valori costali non sono generalmente considerati come la Cosa giusta da fare ™. Non tutti gli usi delle funzioni membro non const non sono validi e restituire constval valori li impedisce in modo verticale.

Ad esempio,

auto it = ++vec.begin(); 

è perfettamente valido, anzi, semantica valide, se non esattamente desiderabile. Considera la mia classe che offre catene di metodi.

class ILikeMethodChains { 
public: 
    int i; 
    ILikeMethodChains& SetSomeInt(int param) { 
     i = param; 
     return *this; 
    } 
}; 
ILikeMethodChains func() { ... } 
ILikeMethodChains var = func().SetSomeInt(1); 

Questo dovrebbe essere vietato solo perché forse, a volte, potremmo chiamare una funzione che non ha senso? No certo che no. O che ne dici di "swaptimization"?

std::string func() { return "Hello World!"; } 
std::string s; 
func().swap(s); 

Questo sarebbe illegale se func() prodotto un'espressione const - ma è perfettamente valido e anzi, supponendo che l'attuazione std::string s' non alloca alcuna memoria del costruttore di default, sia veloce e leggibile/leggibile.

Quello che dovresti capire è che le regole di rvalore/lvalue C++ 03, francamente, non hanno senso. Sono, in effetti, solo parzialmente cotti, e il minimo richiesto per non consentire alcuni torti evidenti pur consentendo alcuni diritti possibili. Le regole del runtime del C++ 0x sono molto più sicure e molto più complete complete.

+0

non è 'const rvalue' a tautology? Voglio dire, non è un valore, che non ha un indirizzo, const per definizione? – davka

+1

@davka: No, non possono semplicemente associarsi a riferimenti non const. Loro stessi non sono affatto in alcun modo. – Puppy

+0

+1 per l'ultimo paragrafo – Josh

1

Se it non è const, mi aspetto che *(++it) mi dia accesso mutevole alla cosa che rappresenta.

Tuttavia, il dereferenziamento di un iteratore const fornisce solo l'accesso non modificabile alla cosa che rappresenta. [modifica: no, this is wrong too. Mi arrendo davvero subito!]

Questo è l'unico motivo per cui posso pensare.

Come lei ha giustamente notare, il seguente è mal formate a causa ++ su un primitivo rese un rvalue (che non può essere sul lato sinistro):

int* p = 0; 
(p++)++; 

Quindi ci sembra essere qualcosa di un'incongruenza nella lingua qui.

+6

Questo è * anche * non quello che l'OP ha chiesto (incidentalmente, dove sono gli altri commenti che lo spiegano? Cancellato?). Il ciclo 'for' non usa nemmeno il comportamento menzionato nella domanda. –

+0

@Konrad: Sì, lo è. (E sì, lo sono.) Il mio ultimo paragrafo spiega la differenza tra ciò che l'OP pensa che un 'const iterator' farebbe, e cosa farebbe effettivamente .. e il motivo per quello che effettivamente fa (che è ciò che ha chiesto per). Il ciclo usa assolutamente il comportamento: la domanda tratta in modo specifico di 'op ++', che dovrebbe (dovrebbe) essere impossibile su un oggetto' const'. Difficile da ripetere con qualcosa che non può essere utilizzato per iterare. –

+0

@Tom Penso che ti sbagli, ma potresti convincermi del contrario di me spiegando cosa intendi nella tua risposta. Inoltre, nota il titolo della domanda modificata. La domanda è completamente estranea agli iteratori (che era solo un esempio), riguarda il tipo di ritorno di 'operator ++'. E anche il tuo commento è sbagliato: 'it' * non è *' const'. Il valore di ritorno di '++ it', però, lo è. Nessun problema qui. –

-1

MODIFICA: Questo non risponde realmente alla domanda come indicato nei commenti. Lascerò il post qui nel caso sia comunque utile ...

Penso che questo sia praticamente un problema di unificazione della sintassi verso un'interfaccia migliore utilizzabile. Quando si forniscono tali funzioni membro senza differenziare il nome e lasciando che solo il meccanismo di risoluzione del sovraccarico determini la versione corretta, si impedisce (o almeno si tenta di) al programmatore di creare preoccupazioni correlate a const.

So che questo potrebbe sembrare contraddittorio, in particolare dato il tuo esempio. Ma se pensi nella maggior parte dei casi d'uso ha senso. Prendi un algoritmo STL come std::equal. Indipendentemente dal fatto che il tuo contenitore sia costante o meno, puoi sempre codificare qualcosa come bool e = std::equal(c.begin(), c.end(), c2.begin()) senza dover pensare alla versione corretta di inizio e fine.

Questo è l'approccio generale in STL. Ricorda di operator[] ... Avendo in mente che i contenitori devono essere utilizzati con gli algoritmi, questo è plausibile. Anche se è anche evidente che in alcuni casi potrebbe essere ancora necessario definire un iteratore con una versione corrispondente (iterator o const_iterator).

Bene, questo è proprio quello che mi viene in mente in questo momento. Non sono sicuro di quanto sia convincente ...

Side note: il modo corretto di utilizzare iteratori costanti è tramite il typedef const_iterator.

+2

Questo è davvero irrilevante. 'std :: equal' prenderà l'iteratore in base al valore, annullando completamente qualsiasi costanza che potrebbe avere quel valore iteratore specifico. – Puppy

+0

@DeadMG - Forse ti sei perso lo spot ...?Il punto è che se ci si trova in un contesto in cui si dispone di un riferimento-a-const in un contenitore e si deve chiamare std :: equal, ad esempio, non è necessario preoccuparsi di chiamare tale c.beginConstVersion(), puoi semplicemente chiamare c.begin() e il compilatore farà il trucco. –

+0

@ Itcmelo: Tranne che ciò viene fatto restituendo un 'const_iterator', non un' const iterator'. La domanda, e la semantica dietro, non hanno nulla a che fare con l'accesso al contenitore: si tratta dell'accesso al * valore * restituito da una funzione. Questo è vero indipendentemente dal fatto che si tratti di un riferimento o di un riferimento const/mutable. – Puppy

Problemi correlati