11

Prenons l'exemple minimale suivante:Ordre de résolution de surcharge de l'opérateur impliquant temporaires

#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; 
} 

La sortie, à la fois sur g ++ et Visual C++, est

hello world 
hello world 
0x4012a4 

La version qui écrit à un objet temporaire , myostream(cout), semble préférer l'opérateur membre ostream::operator<<(void *), au lieu de l'opérateur libre operator<<(ostream &, char *). Il semble faire une différence si l'objet a un nom ou non.

Pourquoi cela se produit-il? Et comment puis-je empêcher ce comportement?

Modifier: Pourquoi cela se produit-il maintenant à partir de diverses réponses? Quant à la façon d'éviter cela, ce qui suit semble appel:

class myostream : public ostream { 
    public: 
     // ... 
     myostream &operator<<(char const *str) { 
      std::operator<<(*this, str); 
      return *this; 
     } 
}; 

Cependant, il en résulte toutes sortes d'ambiguïtés.

+0

Vous pourriez envisager cette réponse à une autre question comme point de départ pour quelque chose c'est au moins semblable à ce que vous voulez réaliser: http://stackoverflow.com/questions/469696/what-is-your-most-useful-cc-snippet/470999#470999 Vous devrez ajouter des fonctionnalités à la classe accepter les modificateurs d'entrée (std :: hex, std :: endl ...), mais cela ne devrait pas être trop dur. –

Répondre

6

rvalues ​​ne peuvent être liés à la référence non-const. Donc dans votre exemple le temporaire de type ostream ne peut pas être le premier argument de l'opérateur libre < < (std :: ostream &, char const *) et ce qui est utilisé est l'opérateur membre < < (void *).

Si vous en avez besoin, vous pouvez ajouter un appel, comme

myostream(cout).flush() << "foo"; 

qui transformera la rvalue en référence.

Notez qu'en C++ 0X, l'introduction de la référence rvalue permettra de fournir une surcharge de l'opérateur < < en prenant des références rvalue comme paramètre, résolvant ainsi la cause première du problème.

+0

Voulez-vous dire "les valeurs ne peuvent pas être liées ..."? et "... transforme le rvalue en une lvalue ..."? –

+0

Bon, j'ai corrigé ma réponse. Merci. – AProgrammer

3

Je viens de réaliser partie de la réponse. Le temporaire n'est pas un lvalue, donc il ne peut pas être utilisé comme un argument de type ostream &.

La question « comment puis-je faire ce travail » reste ...

+0

Le plus simple: fournir des fonctions membres qui font le travail pour vous. –

7

Si un objet ne porte pas de nom (à-dire qu'il est temporaire), il ne peut pas être lié à une référence non-const. Plus précisément, il ne peut pas être lié au premier paramètre de:

operator<<(ostream &, char *) 
+0

.. jusqu'à ce que C++ 0x arrive, et nous autorise avec des références de valeur r. – dirkgently

-1

Eh bien, je ne sais pas la spécification C++ qui provoque, mais il est facile de Suss pourquoi il arrive.

Une durée de vie temporaire sur la pile est généralement transmise à une autre fonction ou une seule opération est appelée. Donc, si vous appelez l'opérateur gratuitement sur elle:

opérateur < < (myostream (Cout))

Il est détruit à la fin de cette opération et le deuxième opérateur « < < » pour ajouter la endl serait référence un objet invalide. La valeur de retour de l'opérateur libre "< <" serait une référence à un objet temporaire détruit. La spécification C++ définit probablement des règles sur les opérateurs libres pour empêcher ce scénario de frustrer et de dérouter les programmeurs C++. Maintenant, dans le cas d'un opérateur membre "< < (void *)" sur le temporaire, la valeur de retour est l'objet lui-même, qui est toujours sur la pile et non détruit, ainsi le compilateur sait ne pas détruire il faut le passer à l'opérateur membre suivant, celui qui prend l'endl. L'enchaînement d'opérateurs sur les temporaires est une fonctionnalité utile pour le code C++ succinct, donc je suis sûr que les concepteurs de spécifications C++ l'ont considéré et ont implémenté le compilateur pour le supporter intentionnellement.

modifier

Certains ont dit qu'il est de faire une référence non-const.Ce code compile:

#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; 
} 

Et il retourne

This works destructing illegal 
+1

Désolé - votre compréhension de la durée de vie des temporaires est erronée. –

+0

N'hésitez pas à m'éclairer. AFAIK, la description ici est cohérente avec mon explication: http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr382. htm. Si vous utilisez une méthode statique sur un temporaire, le temporaire doit être détruit lorsque la pile d'appels pour cette opération est nettoyée. S'il est utilisé comme "ceci" pour une opération membre, alors il peut continuer à vivre après que l'opération soit terminée puisqu'il est en bas de la pile des appels, donc les opérateurs membres chaînés fonctionneront mais les méthodes statiques ne le feront pas. –

+0

Le lien que vous avez posté est une mauvaise source bien connue d'informations C++ (et présente un comportement étrange, me retournant à une autre page). Le standard C++ est la référence ici, et il dit (effectivement) que le temporaire doit traîner jusqu'à la fin de l'expression complète dont il fait partie, qui dans ce cas est la chaîne complète des opérateurs. La vraie raison pour laquelle le code ne fonctionne pas comme prévu est donnée dans ma réponse. –

1

Aucun des réponses à ce jour semblent donner une solution propre, je me contenterai pour la solution sale:

myostream operator<<(myostream stream, char const *str) { 
    std::operator<<(stream, str); 
    return stream; 
} 

Cela n'est possible parce que myostream a un constructeur de copie. (En interne, il est soutenu par un std::stringbuf ref-count.)

0

Alors que C++ 11 résout ce problème car il existe des références rvalue, je pense que cela pourrait être une solution de contournement pour pré-C++ 11.

La solution est d'avoir une fonction membre < < opérateur où l'on peut jeter une référence non-const à la classe de 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; 
     } 
}; 
Questions connexes