2

私は自分のプロジェクト用の単純なロガーラッパーに取り組んでいます。これにより、バックエンドを簡単に交換できます。
これが私の理想的なインターフェースです。

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

私がそれを実装した方法は次のとおりです。

  1. log::error#operator<<Sink一時オブジェクトを返します。

  2. Sink#operator<<移動コンストラクターを返し*this、定義します。

  3. Sink完全なメッセージは、呼び出しチェーンの最後に呼び出されるのデストラクタで 利用できます。

考案された実装:

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

これは、ロギングを無効にするクリーンな方法が必要な場合を除いて、正常に機能します。チェーンを使用していなかった場合、ログ関数はプリプロセッサディレクティブを使用して関数のコンテンツを削除できます

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

次に、コンパイラーは空の関数呼び出しを最適化して削除します。しかし、私が達成しようとしているAPIでそれをどのように行うかはわかりません。glogがどういうわけかそれをするので、私はそれが可能であることを知っています。

このようなディレクティブを使用すると、優れたAPIを使用するという目的が無効になります。

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

これらのタイプの連鎖呼び出しを無効にするクリーンな方法は何ですか?どんな助けでも大歓迎です。

4

3 に答える 3

4

Sinkロギング時に何もしない別のものを実装する必要があります。Glogはこれをヌルストリームと呼んでいます:

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

あなたの場合、単純な実装は次のようになります

#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

これは非常に単純で、コンパイラから離れて最適化できます。

于 2012-11-28T17:06:05.437 に答える
1

それは最もきれいではありませんが、あなたはこのようなことをすることができます:

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


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

これを行うことで少し単純化できます

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


LOG("this" << "is" << "a" << "test");
于 2012-11-28T16:56:54.203 に答える
1

nullストリームを使用する場合でも、ストリームアプローチには1つの問題があります。それは、C++に遅延計算がないことです。

つまり、ストリームが引数に対して何も行わない場合でも、引数は完全に作成されます。

この評価を回避する唯一の方法は、マクロを使用することです。

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

もちろん、マクロを使用する場合は、、、およびをスレッド化する良い機会であることに注意し__func____FILE__ください__LINE__

于 2012-11-28T18:28:08.660 に答える