Ordem de resolução de sobrecarga do operador envolvendo temporários
-
20-09-2019 - |
Pergunta
Considere o seguinte exemplo mínimo:
#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;
}
A saída, tanto no g++ quanto no Visual C++, é
hello world
hello world
0x4012a4
A versão que grava em um objeto temporário, myostream(cout)
, parece preferir o operador membro ostream::operator<<(void *)
, em vez do operador livre operator<<(ostream &, char *)
.Parece fazer diferença se o objeto tem ou não um nome.
Por que isso acontece?E como posso evitar esse comportamento?
Editar:Por que isso acontece agora fica claro a partir de várias respostas.Quanto a como evitar isso, o seguinte parece atraente:
class myostream : public ostream {
public:
// ...
myostream &operator<<(char const *str) {
std::operator<<(*this, str);
return *this;
}
};
No entanto, isso resulta em todos os tipos de ambigüidades.
Solução
rvalues não podem ser vinculados a referências não const.Portanto, no seu exemplo, o temporário do tipo ostream não pode ser o primeiro argumento do operador livre<<(std::ostream&, char const*) e o que é usado é o operador membro<<(void*).
Se precisar, você pode adicionar uma chamada como
myostream(cout).flush() << "foo";
que transformará o rvalue em uma referência.
Observe que em C++ 0X, a introdução da referência de rvalue permitirá fornecer sobrecarga de operador<< tomando referências de rvalue como parâmetro, resolvendo a causa raiz do problema.
Outras dicas
Se um objeto não tiver um nome (ou seja, é temporário), ele não pode ser vinculado a uma referência não consagrada. Especificamente, não pode ser vinculado ao primeiro parâmetro de:
operator<<(ostream &, char *)
Acabei de perceber papel da resposta. O temporário não é um lvalue, portanto não pode ser usado como um argumento do tipo ostream &
.
A pergunta "Como posso fazer isso funcionar" permanece ...
Como nenhuma das respostas até agora parece dar uma solução limpa, vou me contentar com a solução suja:
myostream operator<<(myostream stream, char const *str) {
std::operator<<(stream, str);
return stream;
}
Isso só é possível porque myostream
tem um construtor de cópia. (Internamente, é apoiado por um refinado std::stringbuf
.)
Embora o C ++ 11 resolva esse problema, pois existem referências de RValue, acho que isso pode ser uma solução alternativa para o pré-C ++ 11.
A solução é ter uma função de membro << Operador onde podemos ser lançados em uma referência não consagrada à classe 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;
}
};
Bem, não conheço a especificação C ++ que causa isso, mas é fácil divulgar por que isso acontece.
Uma vida temporária na pilha, geralmente para ser passada para outra função ou ter uma única operação chamada nela. Então, se você ligar para o operador gratuito nele:
Operador << (Myostream (Cout))
Ele é destruído no final desta operação e o segundo operador "<<" para anexar o ENDL referenciaria um objeto inválido. O valor de retorno do operador gratuito "<<" seria uma referência a um objeto temporário destruído. A especificação C ++ provavelmente define regras sobre os operadores gratuitos para impedir que esse cenário frustre e confunda programadores C ++.
Agora, no caso de um operador de membro "<< (void*)" no temporário, o valor de retorno é o próprio objeto, que ainda está na pilha e não destruído, então o compilador sabe não destruí -lo, mas passar para o próximo operador de membro, aquele que leva o final. O encadeamento do operador em temporários é um recurso útil para o código C ++ sucinto, por isso tenho certeza de que os designers de especificações C ++ o consideraram e implementaram o compilador para apoiá -lo intencionalmente.
editar
Alguns disseram que tem a ver com uma referência não consagrada. Este código compila:
#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 retorna
This works destructing illegal