Ordine di risoluzione dell'overload dell'operatore che coinvolge i temporanei
-
20-09-2019 - |
Domanda
Considera il seguente esempio minimo:
#include <iostream>
using namespace std;
class myostream : public ostream {
public:
myostream(ostream const &other) :
ostream(other.rdbuf())
{ }
};
int main() {
cout << "hello world" << endl;
myostream s(cout);
s << "hello world" << endl;
myostream(cout) << "hello world" << endl;
}
L'output, sia su g++ che su Visual C++, è
hello world
hello world
0x4012a4
La versione che scrive su un oggetto temporaneo, myostream(cout)
, sembra preferire l'operatore membro ostream::operator<<(void *)
, invece dell'operatore gratuito operator<<(ostream &, char *)
.Sembra fare la differenza se l'oggetto ha o meno un nome.
Perché succede questo?E come posso prevenire questo comportamento?
Modificare:Il motivo per cui accade è ormai chiaro da varie risposte.Per quanto riguarda come prevenire ciò, sembra interessante quanto segue:
class myostream : public ostream {
public:
// ...
myostream &operator<<(char const *str) {
std::operator<<(*this, str);
return *this;
}
};
Tuttavia, ciò dà luogo a tutti i tipi di ambiguità.
Soluzione
gli rvalue non possono essere associati a riferimenti non costanti.Quindi nel tuo esempio il temporaneo di tipo ostream non può essere il primo argomento dell'operatore libero<<(std::ostream&, char const*) e ciò che viene utilizzato è l'operatore membro<<(void*).
Se ne hai bisogno, puoi aggiungere una chiamata come
myostream(cout).flush() << "foo";
che trasformerà l'rvalue in un riferimento.
Si noti che in C++0X, l'introduzione del riferimento rvalue consentirà di fornire un sovraccarico dell'operatore<< prendendo i riferimenti rvalue come parametro, risolvendo la causa principale del problema.
Altri suggerimenti
Se un oggetto non ha un nome (cioè esso è un temporaneo), non può essere associato a un riferimento non const. In particolare, non può essere vincolata al primo parametro di:
operator<<(ostream &, char *)
Ho appena realizzato parte della risposta. Il temporaneo non è un lvalue, quindi non può essere utilizzato come un argomento di tipo ostream &
.
La domanda "come posso fare questo lavoro" rimane ...
Dal momento che nessuna delle risposte finora sembra dare una soluzione pulita, mi accontenterò della soluzione sporca:
myostream operator<<(myostream stream, char const *str) {
std::operator<<(stream, str);
return stream;
}
Questo è possibile solo perché myostream
ha un costruttore di copia. (Internamente, è sostenuta da un std::stringbuf
ref-contati.)
Mentre C ++ 11 non risolvere il problema in quanto ci sono riferimenti rvalue, credo che questo potrebbe essere una soluzione per il pre-C ++ 11.
La soluzione è quella di avere una funzione membro << operatore dove possiamo lanciare a un riferimento non const alla classe di base:
class myostream : public ostream {
public:
// ...
template<typename T>
ostream &operator<<(const T &t) {
//now the first operand is no longer a temporary,
//so the non-member operators will overload correctly
return static_cast<ostream &>(*this) << t;
}
};
Beh, non so le specifiche C ++ che causa questo, ma è facile da suss fuori perché succede.
A vite temporanee nello stack, di solito per essere passato a un'altra funzione o di avere una singola operazione chiamata su di esso. Quindi, se si chiama l'operatore libero su di esso:
operatore << (myostream (cout))
Si distrutta alla fine di questa operazione e la seconda "<<" all'operatore di aggiungere l'endl sarebbe riferimento ad un oggetto non valido. Il valore restituito dal "<<" operatore libero sarebbe un riferimento a un oggetto temporaneo destructed. Le specifiche C ++ definisce probabilmente regole per gli operatori liberi per evitare questo scenario da programmatori frustrante e confusa C ++.
Ora, nel caso di un "<< (void *)" operatore membro sul temporaneo, il valore restituito è l'oggetto stesso, che è ancora in pila e non distrutto, in modo che il compilatore sa di non distruggere essa ma per passare al successivo operatore punto, quello che prende l'endl. Operatore concatenamento sul provvisori è una caratteristica utile per succinta codice C ++, quindi sono sicuro che i C ++ spec designer ritenuto e attuate al compilatore di sostenerlo intenzionalmente.
modifica
Alcuni hanno detto che è a che fare con un riferimento non-const. Questo codice viene compilato:
#include <iostream>
using namespace std;
class myostream : public ostream {
public:
myostream(ostream const &other) :
ostream(other.rdbuf())
{ }
~myostream() { cout << " destructing "; }
};
int _tmain(int argc, _TCHAR* argv[])
{
basic_ostream<char>& result = std::operator << (myostream(cout), "This works");
std::operator << (result, "illegal");
return 0;
}
E ritorna
This works destructing illegal