2012-11-28 2 views
2

Je travaille sur un simple gestionnaire de journalisation pour mes projets, ce qui me permet d'échanger facilement le backend.
Ceci est mon interface idéale:C++ désactive l'invocation chaînée sans retour à la ligne de commande

log::error << "some" << " log " << "message"; 

La façon dont je mis en œuvre était:

  1. log::error#operator<< retourne un objet Sink temporaire.

  2. Sink#operator<< renvoie *this et définit un constructeur de déplacement. Le message complet peut être utilisé dans le destructeur Sink qui est appelé à la fin de la chaîne d'invocation.

Contrived Mise en œuvre:

#include <iostream> 
#include <string> 

struct Sink { 

    Sink (std::string const& msg) : m_message(msg) {} 

    // no copying 
    Sink (Sink const& orig) = delete; 

    // move constructor 
    Sink (Sink && orig) : m_message(std::move(orig.m_message)) {}; 

    // use the complete string in the destructor 
    ~Sink() { std::cerr << m_message << std::endl;} 

    Sink operator<< (std::string const& msg) { 
    m_message.append(msg); 
    return std::move(*this); 
    } 

    std::string m_message; 
}; 

struct Level { 
    Sink operator<< (std::string const& msg) { return Sink(msg); } 
}; 

int main() { 
    Level log; 

    log << "this" << " is " << "a " << "test"; 
} 

Cela fonctionne bien sauf que je besoin d'un moyen propre de désactiver la journalisation. Si je n'utilisais pas Enchaînement, ma fonction de journal pourrait utiliser une directive pré-processeur pour supprimer la fonction du contenu de

void log (std::string) { 
    #ifdef LOGGING_ENABLED 
    // log message 
    #endif 
} 

Le compilateur alors d'optimiser et de supprimer l'appel de fonction vide. Mais je ne sais pas comment je ferais ça avec l'API que j'essaye d'atteindre. Je sais que c'est possible parce que glog le fait d'une manière ou d'une autre.

L'utilisation de directives comme celle-ci va à l'encontre du but d'avoir une bonne API.

#ifdef LOGGING_ENABLED 
    log << "this" << " is " << "a " << "test"; 
#endif 

Quelle est une manière propre de désactiver ces types d'appels chaînés? Toute aide est appréciée.

Répondre

4

Vous devez imlement autre Sink qui ne fait rien lors de la connexion . Glog appelle cela un flux nul:

// A class for which we define operator<<, which does nothing. 
class GOOGLE_GLOG_DLL_DECL NullStream : public LogMessage::LogStream { 
public: 
    // Initialize the LogStream so the messages can be written somewhere 
    // (they'll never be actually displayed). This will be needed if a 
    // NullStream& is implicitly converted to LogStream&, in which case 
    // the overloaded NullStream::operator<< will not be invoked. 
    NullStream() : LogMessage::LogStream(message_buffer_, 1, 0) { } 
    NullStream(const char* /*file*/, int /*line*/, 
      const CheckOpString& /*result*/) : 
     LogMessage::LogStream(message_buffer_, 1, 0) { } 
    NullStream &stream() { return *this; } 
private: 
    // A very short buffer for messages (which we discard anyway). This 
    // will be needed if NullStream& converted to LogStream& (e.g. as a 
    // result of a conditional expression). 
    char message_buffer_[2]; 
}; 

// Do nothing. This operator is inline, allowing the message to be 
// compiled away. The message will not be compiled away if we do 
// something like (flag ? LOG(INFO) : LOG(ERROR)) << message; when 
// SKIP_LOG=WARNING. In those cases, NullStream will be implicitly 
// converted to LogStream and the message will be computed and then 
// quietly discarded. 
template<class T> 
inline NullStream& operator<<(NullStream &str, const T &) { return str; } 

Dans votre cas, une implémentation simple ressemblerait

#ifdef LOGGING_ENABLED 
    /* your sink */ 
#else 
    struct Sink { 
    Sink (std::string) {} 
    Sink (Sink const& orig) {}; 
    }; 
    template <typename T> Sink operator<<(Sink s, T) { return s; } 
#endif 

Ceci est très simple et peut être optimisé loin du compilateur.

+0

awesome, mais le constructeur de copie 'Sink' factice n'a pas pu être supprimé, ou il devrait y avoir un constructeur de déplacement. –

1

Ce n'est pas la plus jolie, mais vous pouvez faire quelque chose comme ceci:

#ifdef LOGGING_ENABLED 
#define LOG(message) message 
#else 
#define LOG(message) 
#endif 


LOG(log << "this" << "is" << "a" << "test"); 

Vous pouvez simplifier un peu en faisant cela

#ifdef LOGGING_ENABLED 
#define LOG(message) log << message 
#else 
#define LOG(message) 
#endif 


LOG("this" << "is" << "a" << "test"); 
1

Il y a un problème avec l'approche de flux, même avec un flux nul: il n'y a pas de calcul paresseux en C++. C'est-à-dire, même si votre flux ne fait rien avec l'argument, l'argument est toujours entièrement créé.

La seule façon d'éviter cette évaluation est d'utiliser une macro:

#define LOG(Message_) \ 
    do (LogManager::activated()) { 
     logger << Message_; 
    } while(0); 

Bien sûr, je tiens à souligner que si vous utilisez une macro, il est une bonne occasion putain de fil dans __func__, __FILE__ et __LINE__ .

Questions connexes