2008-11-14 13 views
5

Original QuestionComment surcharger l'opérateur << qui ne prend pas ou retourner ostream

J'ecrivais une classe d'exploitation forestière dont le but est de pouvoir faire:

// thread one 
Logger() << "Some string" << std::ios::hex << 45; 
// thread two 
Logger() << L"Some wide string" << std::endl; 

Actuellement mon en-tête de l'enregistreur ressemble à ceci:

#pragma once; 
#include <ostream>  
class Logger 
{ 
public: 
    Logger(); 
    ~Logger(); 

    std::ostream* out_stream; 
}; 

template <typename T> 
Logger& operator<< (Logger& logger, T thing) { 
    *logger.out_stream << thing; 
    return logger; 
} 

Quelques notes sur cette classe:

  1. La compatibilité multiplateforme n'est pas un problème.
  2. À l'intérieur de Logger.cpp, il y a une classe singleton qui prend soin de créer le "vrai" ostream.
  3. Le constructeur de l'enregistreur et le déconstructeur effectuent le verrouillage nécessaire du singleton.

J'ai trois problèmes:

  • Comment puis-je l'opérateur < < fonction d'un ami ou un membre pour que je puisse mettre out_stream en privé?
  • Comment faire pour que la fonction < < de l'opérateur fonctionne pour les manipulateurs?
  • Comment puis-je ajouter une spécialisation de sorte que si T est un WCHAR * ou un std :: wstring qu'il va le convertir en char * ou std :: string avant de le transmettre à out_stream? (Je peux faire la conversion Perdre les personnages de haut unicode est pas un problème dans mon cas..)

Résumé des choses apprises dans les réponses:

  • Mettre modèle avant ami et non après.
  • std :: ios :: hex n'est pas un manipulateur. std :: hex est un manipulateur.

Résultat final

#pragma once 
#include <ostream> 
#include <string> 

std::string ConvertWstringToString(std::wstring wstr); 

class Logger 
{ 
public: 
    Logger(); 
    ~Logger(); 

    template <typename T> 
    Logger& operator<< (T data) { 
     *out << data; 
     return *this; 
    } 
    Logger& operator<< (std::wstring data) { 
     return *this << ConvertWstringToString(data); 
    } 
    Logger& operator<< (const wchar_t* data) { 
     std::wstring str(data); 
     return *this << str; 
    } 

private: 
    std::ostream* out; 
}; 
+0

Votre résultat est incorrect. Il suffit de les surcharger à la place (omettre le modèle <> partie). Cela est nécessaire dans la réponse d'Adam (qui les spécialise dans l'espace des espaces de noms), sinon les fonctions (alors normales) ne sont plus des amis. –

+0

intéressant, il a travaillé avec eux là-bas. Mais, juste pour être correct, je les ai enlevés quand même. Merci! –

Répondre

7

Vous pouvez utiliser ami définition, qui définira l'opérateur dans l'espace de noms environnant de la classe, et rend visible uniquement à la résolution de surcharge opérateur (non appelable manuellement en utilisant la syntaxe :: opérateur < < ...):

L'alternative, de garder votre code tel qu'il est et juste faire l'opérateur < < modèle à un ami, vous ajoutez cette ligne dans votre définition de classe:

template <typename T> 
friend Logger& operator<< (Logger& logger, T thing); 

Pour le problème de manipulation, je vais juste donner vous mon code que j'écris il y a quelque temps:

#include <iostream> 
#include <cstdlib> 
using namespace std; 

template<typename Char, typename Traits = char_traits<Char> > 
struct logger{ 
    typedef std::basic_ostream<Char, Traits> ostream_type; 
    typedef ostream_type& (*manip_type)(ostream_type&); 
    logger(ostream_type& os):os(os){} 
    logger &operator<<(manip_type pfn) { 
     if(pfn == static_cast<manip_type>(std::endl)) { 
      time_t t = time(0); 
      os << " --- " << ctime(&t) << pfn; 
     } else 
      os << pfn; 
     return *this; 
    } 
    template<typename T> 
    logger &operator<<(T const& t) { 
     os << t; 
     return *this; 
    } 
private:   
    ostream_type & os; 
}; 

namespace { logger<char> clogged(cout); } 
int main() { clogged << "something with log functionality" << std::endl; } 

}; Notez que c'est std :: hex, mais pas std :: ios :: hex.

Ce dernier est utilisé comme indicateur de manipulateur pour la fonction setf des flux. Notez que pour votre exemple, aucun traitement spécial des manipulateurs n'est requis. Le traitement spécial ci-dessus de std :: endl est seulement nécessaire parce que je fais le flux en plus de l'heure quand std :: endl est utilisé.

+0

Argh! Pas étonnant que je recevais 204845 dans le journal quand je l'ai fait. Il semble que les manipulateurs fonctionnent comme ils sont. Merci! –

0

pourquoi ne pas faire le chemin printf et utiliser la méthode multi-paramètres (avec les trois points ...). Cela vous donne encore beaucoup de puissance de formage et ne le rend pas aussi désordonné que lorsque vous utilisez le < <.

Par exemple:

Logger("This is my log msg %0X", 45); 

Attendez deux secondes et tirez mal un exemple de code pour vous.

Edit:

void Logger(const char* format, ...) 
{ 
    char szMsg[3000]; 

    va_list args; 
    va_start(args, format); 
    vsnprintf(szMsg, sizeof(szMsg) - 1, format, args); 
    va_end(args); 

    // code to print szMsg to a file or whatever here 
} 

Si vous voulez l'utiliser comme une classe non pas une fonction autonome, vous pouvez surcharger l'opérateur enregistreur() et il fonctionnera même

+0

La meilleure suggestion! Il y a deux raisons pour lesquelles je veux utiliser le format <<. L'un est moi que je préfère la lisibilité des enchaînements <<. L'autre est que je veux éviter la création de beaucoup de tampons char. –

+0

Il y a plusieurs raisons pour ne pas utiliser la méthode varargs dans le code C++, notamment qu'elle supprime la sécurité du type, met un couplage étroit entre l'appelant/appelé et le comportement indéfini des UDT (même s'ils sont toujours autorisés). – twokats

+0

Bon à savoir. Jamais un fan de << en C++ – Lodle

2

L'utilisation d'un modèle est la bonne façon de le faire, mais vous avez juste à vous assurer que le modèle est dans le fichier d'en-tête (logger.h, ou tout ce que vous avez appelé), pas dans le fichier de mise en œuvre (logger.cpp) . Cela fonctionnera automatiquement pour tout type qui a operator << défini avec un std::ostream. Il fonctionnera aussi automatiquement avec les objets du manipulateur de flux - ce sont juste des fonctions qui prennent un std::ostream paramètre, et operator << appelle simplement la fonction sur le ostream.

Vous pouvez faire operator << une fonction ami comme suit:

template <typename T> friend Logger& operator<< (Logger& logger, T thing); 

Spécialisations sont faciles - il suffit d'utiliser les spécialisations de modèle (encore une fois, dans le fichier d'en-tête):

template <typename T> 
Logger& operator<< (Logger& logger, T thing) { 
    *logger.out_stream << thing; 
    return logger; 
} 

// Template specialization - the "template <>" part is necessary 
template <> 
Logger& operator<< (Logger& logger, const wchar_t *wstr) 
{ 
    // convert wstr to an ANSI string and log it 
} 

template <> 
Logger& operator<< (Logger& logger, const std::wstring & wstr) 
{ 
    // convert wstr to an ANSI string and log it 
} 
2

Aucune déclaration d'amitié nécessaire:

class Logger 
{ 
public: 
    Logger(); 
    ~Logger(); 

template <typename T> 
inline Logger& Display(T thing) 
{ 
    *out_stream << thing; 
    return *this; 
} 
private: 
    std::ostream* out_stream; 
}; 

template <typename T> 
Logger& operator<< (Logger& logger, T thing) 
{ 
    return logger.Display(thing); 
}