c++ - Come mai un riferimento non const non può legarsi a un oggetto temporaneo?




reference temporary c++-faq (10)

Perché non è possibile ottenere un riferimento non const a un oggetto temporaneo, quale funzione getx() restituisce? Chiaramente, questo è proibito da C ++ Standard, ma sono interessato allo scopo di tale restrizione, non un riferimento allo standard.

struct X
{
    X& ref() { return *this; }
};

X getx() { return X();}

void g(X & x) {}    

int f()
{
    const X& x = getx(); // OK
    X& x = getx(); // error
    X& x = getx().ref(); // OK
    g(getx()); //error
    g(getx().ref()); //OK
    return 0;
}
  1. È chiaro che la durata dell'oggetto non può essere la causa, poiché il riferimento costante a un oggetto non è vietato da C ++ Standard.
  2. È chiaro che l'oggetto temporaneo non è costante nell'esempio sopra, poiché le chiamate a funzioni non costanti sono consentite. Ad esempio, ref() potrebbe modificare l'oggetto temporaneo.
  3. Inoltre, ref() ti permette di ingannare il compilatore e ottenere un collegamento a questo oggetto temporaneo e questo risolve il nostro problema.

Inoltre:

Dicono che "l'assegnazione di un oggetto temporaneo al riferimento const estende la durata di questo oggetto" e "Non si dice nulla sui riferimenti non-const". La mia domanda aggiuntiva Il seguente incarico estende la durata dell'oggetto temporaneo?

X& x = getx().ref(); // OK

Answers

Perché vorresti mai X& x = getx(); ? Basta usare X x = getx(); e fare affidamento su RVO.


Il problema principale è questo

g(getx()); //error

è un errore logico: g sta modificando il risultato di getx() ma non hai alcuna possibilità di esaminare l'oggetto modificato. Se g non ha avuto bisogno di modificare il suo parametro allora non avrebbe richiesto un riferimento lvalue, avrebbe potuto prendere il parametro per valore o per riferimento const.

const X& x = getx(); // OK

è valido perché a volte hai bisogno di riutilizzare il risultato di un'espressione, ed è abbastanza chiaro che hai a che fare con un oggetto temporaneo.

Tuttavia non è possibile farlo

X& x = getx(); // error

valido senza rendere valido g(getx()) , che è ciò che i progettisti linguistici cercavano di evitare in primo luogo.

g(getx().ref()); //OK

è valido perché i metodi conoscono solo la costanza di this , non sanno se sono chiamati su un lvalue o su un valore di rvalue.

Come sempre in C ++, hai una soluzione per questa regola, ma devi segnalare al compilatore che sai cosa stai facendo essendo esplicito:

g(const_cast<x&>(getX()));

Sembra che la domanda iniziale sul motivo per cui questo non è consentito ha avuto una risposta chiara: "perché è più probabile un errore".

FWIW, ho pensato di mostrare come si poteva fare, anche se non penso che sia una buona tecnica.

Il motivo per cui a volte voglio passare un metodo temporaneo a un metodo che utilizza un riferimento non const è quello di gettare intenzionalmente un valore restituito da riferimento che il metodo chiamante non interessa. Qualcosa come questo:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

Come spiegato nelle risposte precedenti, questo non viene compilato. Ma questo compila e funziona correttamente (con il mio compilatore):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

Questo mostra che puoi usare casting per mentire al compilatore. Ovviamente, sarebbe molto più pulito dichiarare e passare una variabile automatica non utilizzata:

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

Questa tecnica introduce una variabile locale non necessaria nello scopo del metodo. Se per qualche motivo si desidera impedire che venga utilizzato in un secondo momento nel metodo, ad esempio per evitare confusione o errori, è possibile nasconderlo in un blocco locale:

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

-- Chris


La soluzione alternativa è la parola chiave 'mutabile'. In realtà essere cattivi è lasciato come esercizio per il lettore. Oppure vedere qui: http://www.ddj.com/cpp/184403758


"È chiaro che l'oggetto temporaneo non è costante nell'esempio sopra, poiché sono consentite chiamate a funzioni non costanti, ad esempio ref () potrebbe modificare l'oggetto temporaneo."

Nel tuo esempio getX () non restituisce una const X così puoi chiamare ref () più o meno allo stesso modo in cui potresti chiamare X (). Ref (). Si sta restituendo un riferimento non const e quindi si possono chiamare metodi non const, ciò che non si può fare è assegnare il riferimento a un riferimento non const.

Insieme al commento di SadSidos, ciò rende errati i tre punti.


Perché è discusso nelle FAQ C ++ ( grassetto in mio):

In C ++, i riferimenti non const possono essere associati a lvalue e i riferimenti const possono essere associati a lvalue o rvalues, ma non c'è nulla che possa essere associato a un valore non const. Questo serve a proteggere le persone dal cambiare i valori dei beni temporanei che vengono distrutti prima che il loro nuovo valore possa essere usato . Per esempio:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

Se quel incr (0) fosse permesso o qualche temporaneo che nessuno avesse mai visto sarebbe stato incrementato o - molto peggio - il valore di 0 diventerebbe 1. Quest'ultimo sembra sciocco, ma in realtà c'era un bug simile nei primi compilatori di Fortran che a parte una posizione di memoria per contenere il valore 0.


Nel tuo codice getx() restituisce un oggetto temporaneo, un cosiddetto "rvalore". Puoi copiare rvalues ​​in oggetti (cioè variabili) o legarli a riferimenti const (che estenderanno la loro durata fino alla fine della vita del riferimento). Non è possibile associare rvalues ​​a riferimenti non const.

Questa è stata una decisione progettuale intenzionale al fine di impedire agli utenti di modificare accidentalmente un oggetto che sta per morire alla fine dell'espressione:

g(getx()); // g() would modify an object without anyone being able to observe

Se vuoi farlo, dovrai prima fare una copia locale o dell'oggetto o associarlo a un riferimento const:

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

Si noti che il prossimo standard C ++ includerà riferimenti rvalue. Ciò che conosci come riferimenti sta quindi diventando per essere chiamato "riferimenti lvalue". Ti sarà consentito legare i valori di rvalue ai riferimenti di rvalue e puoi sovraccaricare le funzioni su "rvalore-ness":

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

L'idea alla base dei riferimenti di valore è che, poiché questi oggetti stanno per morire comunque, puoi sfruttare questa conoscenza e implementare quella che viene chiamata "semantica del movimento", un certo tipo di ottimizzazione:

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty

Da questo articolo del blog Visual C ++ sui riferimenti rvalue :

... C ++ non vuole che tu modifichi accidentalmente i temporari, ma chiamare direttamente una funzione membro non const su un valore modificabile è esplicito, quindi è permesso ...

Fondamentalmente, non dovresti provare a modificare i provvisori solo perché sono oggetti temporanei e moriranno da un momento all'altro. Il motivo per cui è permesso chiamare metodi non const è che, beh, sei il benvenuto a fare alcune cose "stupide" purché tu sappia cosa stai facendo e tu ne sia esplicito (come, usando reinterpret_cast). Ma se leghi un riferimento temporaneo a un non-const, puoi continuare a lasciarlo in giro "per sempre" solo per far sparire la tua manipolazione dell'oggetto, perché da qualche parte lungo la strada hai completamente dimenticato che era temporaneo.

Se fossi in te, ripenserò il design delle mie funzioni. Perché g () accetta riferimenti, modifica il parametro? Se no, falla riferimento const, se sì, perché provi a passare temporaneamente ad esso, non ti interessa che sia temporaneo che stai modificando? Perché getx () restituisce temporaneamente comunque? Se condividi con noi il tuo scenario reale e quello che stai cercando di ottenere, potresti ricevere alcuni buoni suggerimenti su come farlo.

Andare contro la lingua e ingannare il compilatore raramente risolve i problemi - di solito crea problemi.

Modifica: risposta alle domande nel commento: 1) X& x = getx().ref(); // OK when will x die? X& x = getx().ref(); // OK when will x die? - Non lo so e non mi interessa, perché questo è esattamente ciò che intendo per "andare contro la lingua". Il linguaggio dice "i temporaries muoiono alla fine dell'istruzione, a meno che non siano legati al riferimento const, nel qual caso muoiono quando il riferimento esce dall'ambito di applicazione". Applicando questa regola, sembra che x sia già morto all'inizio della prossima istruzione, dal momento che non è legato al riferimento const (il compilatore non sa cosa restituisce ref ()). Questa è solo una supposizione comunque.

2) Ho affermato chiaramente lo scopo: non ti è permesso modificare i temporari, perché non ha senso (ignorando i riferimenti di runtime C ++ 0x). La domanda "allora perché sono autorizzato a chiamare membri non costanti?" è buono, ma non ho una risposta migliore di quella che ho già detto sopra.

3) Bene, se ho ragione su x in X& x = getx().ref(); morendo alla fine della dichiarazione, i problemi sono evidenti.

Ad ogni modo, in base alla tua domanda e ai tuoi commenti, non penso che nemmeno queste risposte extra ti soddisfino. Ecco un ultimo tentativo / riassunto: il comitato del C ++ ha deciso che non ha senso modificare i provvisori, pertanto non hanno consentito il binding ai riferimenti non const. Potrebbe esserci qualche implementazione del compilatore o sono stati coinvolti anche problemi storici, non lo so. Poi, è emerso un caso specifico, e si è deciso che contro tutte le probabilità, permetteranno comunque la modifica diretta attraverso la chiamata al metodo non const. Ma questa è un'eccezione: generalmente non ti è permesso modificare i provvisori. Sì, il C ++ è spesso così strano.


Ottima domanda, ed ecco il mio tentativo di una risposta più concisa (dal momento che molte informazioni utili sono nei commenti e difficili da scavare nel rumore).

Qualsiasi riferimento diretto direttamente ad un temporaneo estenderà la sua vita [12.2.5]. D'altra parte, un riferimento inizializzato con un altro riferimento non lo farà (anche se alla fine è lo stesso temporaneo). Questo ha senso (il compilatore non sa a cosa si riferisce in definitiva quel riferimento).

Ma questa idea è estremamente confusa. Ad esempio const X &x = X(); farà sì che il temporaneo duri quanto il riferimento x , ma const X &x = X().ref(); NON (chissà cosa ref() effettivamente restituito). In quest'ultimo caso, il distruttore per X viene chiamato alla fine di questa riga. (Questo è osservabile con un distruttore non banale.)

Quindi sembra generalmente confuso e pericoloso (perché complicare le regole sulla vita degli oggetti?), Ma presumibilmente c'era bisogno almeno di riferimenti const, quindi lo standard ha impostato questo comportamento per loro.

[Dal commento sbi ]: Si noti che il fatto che il collegamento a un riferimento const migliori le durate di un temporaneo è un'eccezione che è stata aggiunta deliberatamente (TTBOMK per consentire ottimizzazioni manuali). Non è stata aggiunta un'eccezione per i riferimenti non const, poiché è stato visto che associare un riferimento temporaneo a un non const è molto probabilmente un errore del programmatore.

Tutti i provvisori persistono fino alla fine dell'espressione completa. Per farne uso, tuttavia, hai bisogno di un trucco come quello che hai con ref() . È legale. Non sembra esserci una buona ragione per far passare il cerchio extra, tranne per ricordare al programmatore che sta accadendo qualcosa di insolito (vale a dire, un parametro di riferimento le cui modifiche saranno rapidamente perse).

[Un altro commento sbi ] La ragione per cui Stroustrup dà (in D & E) per non consentire il binding di rvalues ​​a riferimenti non const è che, se Alexey's g () modificasse l'oggetto (che ci si aspetterebbe da una funzione prendendo un non const riferimento), modificherebbe un oggetto che sta per morire, quindi nessuno potrebbe ottenere comunque il valore modificato. Dice che questo, molto probabilmente, è un errore.


Il pacchetto Visual Studio NuGet deve essere aggiornato per la nuova versione del toolset

Ho appena avuto questo problema cercando di collegare libpng con Visual Studio 2013. Il problema è che il file del pacchetto aveva solo librerie per Visual Studio 2010 e 2012.

La soluzione corretta è sperare che lo sviluppatore rilasci un pacchetto aggiornato e quindi esegua l'aggiornamento, ma ha funzionato per me hackerando un'impostazione extra per VS2013, indicando i file della libreria VS2012.

Ho modificato il pacchetto (nella cartella dei packages all'interno della directory della soluzione) trovando packagename\build\native\packagename.targets e all'interno di quel file, copiando tutte le sezioni v110 . Ho cambiato la v110 in v120 nei campi delle condizioni facendo molta attenzione a lasciare i percorsi del nome file tutti come v110 . Questo ha semplicemente permesso a Visual Studio 2013 di collegarsi alle librerie per il 2012, e in questo caso ha funzionato.







c++ reference const temporary c++-faq