Domanda

Quando compilo il seguente codice usando g++

class A {};

void foo(A&) {}

int main()
{
  foo(A());
  return 0;
}

Ricevo i seguenti messaggi di errore:

> g++ test.cpp -o test     
test.cpp: In function ‘int main()’:
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’

Dopo qualche riflessione, questi errori hanno molto senso per me. A() è solo un valore temporaneo, non una posizione assegnabile nello stack, quindi non sembra avere un indirizzo. Se non ha un indirizzo, non posso tenere un riferimento ad esso. Va bene, va bene.

Ma aspetta! Se aggiungo il seguente operatore di conversione alla classe A

class A
{
public:
  operator A&() { return *this; }
};

allora va tutto bene! La mia domanda è se questo sia anche remoto. Cosa indica esattamente this quando const è costruito come valore temporaneo?

Mi è stato dato un po 'di fiducia dal fatto che

void foo(const A&) {}

può accettare valori temporanei in base a const A& e a tutti gli altri compilatori che ho usato. La parola chiave A& può sempre essere scartata, quindi mi sorprenderebbe se ci fossero effettive differenze semantiche tra un parametro <=> e un parametro <=>. Quindi immagino che sia un altro modo di porre la mia domanda: perché un <=> riferimento a un valore temporaneo è considerato sicuro dal compilatore mentre un riferimento non - <=> non lo è?

È stato utile?

Soluzione

Non è che un indirizzo non possa essere preso (il compilatore potrebbe sempre ordinarlo inserito nello stack, cosa che fa con ref-to-const), è una questione di intenti dei programmatori. Con un'interfaccia che accetta un A & Amp ;, sta dicendo & Quot; Modificherò ciò che è in questo parametro in modo da poter leggere dopo la chiamata di funzione & Quot ;. Se lo passi temporaneamente, allora la cosa & Quot; modificata & Quot; non esiste dopo la funzione. Questo è (probabilmente) un errore di programmazione, quindi non è consentito. Ad esempio, considera:

void plus_one(int & x) { ++x; }

int main() {
   int x = 2;
   float f = 10.0;

   plus_one(x); plus_one(f);

   cout << x << endl << f << endl;
}

Questo non viene compilato, ma se i provvisori potessero legarsi a un ref-to-non-const, si compilerebbe ma avrebbe risultati sorprendenti. In plus_one (f), f verrebbe implicitamente convertito in un int temporaneo, plus_one prenderebbe la temperatura e la incrementerebbe, lasciando intatto il float sottostante f. Quando plus_one è tornato, non avrebbe avuto effetto. Questo non è quasi certamente quello che intendeva il programmatore.


La regola occasionalmente sbaglia. Un esempio comune (descritto qui ) , sta tentando di aprire un file, stampare qualcosa e chiuderlo. Dovresti essere in grado di fare:

ofstream("bar.t") << "flah";

Ma non puoi perché l'operatore < < prende un ref-to-non-const. Le opzioni sono suddivise in due righe o chiama un metodo che restituisce ref-to-non-const:

ofstream("bar.t").flush() << "flah";

Altri suggerimenti

Quando assegni un valore r a un riferimento const, hai la certezza che il temporaneo non verrà distrutto fino a quando il riferimento non verrà distrutto. Quando si assegna a un riferimento non const, non viene fornita tale garanzia.

int main()
{
   const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope.
   A& a1 = A(); // You can't do this.
}

Non puoi gettare via con sicurezza la voluttà o no e aspettarti che le cose funzionino. Esistono diverse semantiche sui riferimenti const e non const.

Un gotcha in cui alcune persone potrebbero imbattersi: il compilatore MSVC (compilatore Visual Studio, verificato con Visual Studio 2008) compilerà questo codice senza problemi. Avevamo usato questo paradigma in un progetto per funzioni che di solito prendevano un argomento (un blocco di dati da digerire), ma a volte volevano cercare il blocco e restituire risultati al chiamante. L'altra modalità è stata abilitata prendendo tre argomenti --- il secondo argomento era l'informazione su cui cercare (riferimento predefinito alla stringa vuota), e il terzo argomento era per i dati di ritorno (riferimento predefinito all'elenco vuoto del tipo desiderato).

Questo paradigma ha funzionato in Visual Studio 2005 e 2008 e abbiamo dovuto riformattarlo in modo che l'elenco fosse creato e restituito invece di essere posseduto da un chiamante e mutato per essere compilato con g ++.

Se c'è un modo per impostare gli switch del compilatore in modo da non consentire questo tipo di comportamento in MSVC o consentirlo in g ++, sarei entusiasta di saperlo; la permissività del compilatore MSVC / la restrittività del compilatore g ++ aggiunge complicazioni al codice di porting.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top