親愛なるKhabrovites、私は出版に生まれました-質問。 コメントであなたの批判を聞いてうれしいです。
このプロジェクトでは、既存のロギングシステムの小さなラッパーをスケッチする必要があり、その主な目的に加えて、出力ストリームを操作するスタイルで使用したかったのです。
不要な詳細を省略すると、ラッパーは次のようになります。
class LoggerWrap: public RequiredInterface { public: void write(const std::string& str) const; // /* ... RequiredInterface ... */ };
そして、私もこのようなものを使用したいです:
LoggerWrap log; log << " 1: " << std::setw(10) << someValue << "; 2:" << std::setw(15) << anotherValue;
思考の過程で、私は議論したいいくつかの決定を思いつきました。
オプション1
オーバーロード演算子<<。受信したデータは、ストリームのローカルostringstreamに送信する必要があります。 最後に、特別なオブジェクトをログに書き込みます-行の終わりのサイン。
ソリューションは次のようになります。
class LoggerWrap { public: void write(const std::string& str) const { // std::cout << "[Log]: " << str << std::endl; } /* ... ... */ struct Flush {}; // template<typename T> LoggerWrap& operator<< (const T& data) { buf << data; return *this; } LoggerWrap& operator<< (const Flush&) { write(buf.str()); buf.str(""); buf.flags(defFmtFlags); return *this; } private: thread_local static std::ostringstream buf; const thread_local static std::ios::fmtflags defFmtFlags; }; thread_local std::ostringstream LoggerWrap::buf; const thread_local std::ios::fmtflags LoggerWrap::defFmtFlags(buf.flags());
使用法:
LoggerWrap logger; logger << "#" << 1 << ": " << 1.2 << ", text again" << LoggerWrap::Flush(); logger << "#" << 2 << ": " << std::scientific << 2.3 << ", text again" << LoggerWrap::Flush(); logger << "string #" << 3 << ": " << 10.5 << LoggerWrap::Flush();
コンソールが印刷されます:
[Log]: #1: 1.2, text again [Log]: #2: 2.300000e+00, text again [Log]: #3: 10.5
私にとって、このオプションの欠点はLoggerWrap :: Flush()を書く必要があることです。 あなたはそれを書くことを忘れて、それから長い間、そして地獄がログで何が起こっているかを理解しようとすることができます。
オプション2
これを明示的に示すことなく、ロギングの行が完全であると判断する方法は? 私は一時的なオブジェクトの寿命に頼ることにしました。 関数がオブジェクトを返すとき、それへのリンクがある間、それは存続します。 したがって、このオブジェクトへのリンクを返す演算子<<を使用して一時オブジェクトを作成でき、デストラクタはLoggerWrap :: writeメソッドを呼び出します。
次のことが判明しました。
class LoggerWrap { public: void write(const std::string& str) const { // std::cout << "[Log]: " << str << std::endl; } /* ... ... */ class TmpLog { friend class LoggerWrap; public: ~TmpLog() { if (flush) { logger.write(buf.str()); buf.str(""); } } template<typename T> TmpLog& operator<< (const T& data) { buf << data; return *this; } TmpLog(const TmpLog&) = delete; private: TmpLog(const LoggerWrap& logger, std::ostringstream& buf) : logger(logger), buf(buf) { } TmpLog(TmpLog&& that): logger(that.logger), buf(that.buf), flush(that.flush) { that.flush = false; } const LoggerWrap& logger; std::ostringstream& buf; bool flush = true; }; template<typename T> TmpLog operator<< (const T& data) { buf.flags(defFmtFlags); TmpLog tmlLog(*this, buf); return std::move(tmlLog << data); } private: thread_local static std::ostringstream buf; const thread_local static std::ios::fmtflags defFmtFlags; }; thread_local std::ostringstream LoggerWrap::buf; const thread_local std::ios::fmtflags LoggerWrap::defFmtFlags(buf.flags());
そして使用:
LoggerWrap logger; logger << "#" << 1 << ": " << 1.2 << ", text again"; logger << "#" << 2 << ": " << std::scientific << 2.3 << ", text again"; logger << "#" << 3 << ": " << 10.5;
コンソールの出力は、最初のソリューションに似ています。
まとめ
ロギングシステムはこれらを区別しないため、ロギングレベル(警告、エラー、情報など)はここでは設定されませんが、これをラッパーに追加することは難しくありません。 たとえば、LoggerWrap ::演算子()を定義します。これは、出力する文字列に必要なレベルを引数として受け取ります。
各行の後にマジックワードを追加する必要がないので、2番目のソリューションを好みます。 また、ログレベルを追加するときに、一時オブジェクトにこの情報を現在の行に保存することができます。
読んでくれてありがとう、コメントであなたの意見を見てほしい。