8

私たちのプロジェクトでは、マクロを使用して、次のように1行のステートメントでロギングを簡単かつ簡単にします。

DEBUG_LOG(TRACE_LOG_LEVEL, "The X value = " << x << ", pointer = " << *x);

マクロは2番目のパラメーターをstringstream引数に変換し、それを通常のC++ロガーに送信します。これは、マルチパラメータのロギングステートメントを非常に簡潔にするため、実際にはうまく機能します。ただし、Scott Meyersは、Effective C ++ 3rd Editionで、「インライン関数のテンプレートを使用することで、マクロのすべての効率に加えて、通常の関数のすべての予測可能な動作と型安全性を得ることができます」と述べています(項目2)。予測可能な動作に関連するC++でのマクロの使用には多くの問題があることを知っているので、コードベースでできるだけ多くのマクロを排除しようとしています。

私のロギングマクロは次のように定義されています:

#define DEBUG_LOG(aLogLevel, aWhat) {  \
if (isEnabled(aLogLevel)) {            \
  std::stringstream outStr;            \
  outStr<< __FILE__ << "(" << __LINE__ << ") [" << getpid() << "] : " << aWhat;    \
  logger::log(aLogLevel, outStr.str());    \
}

私はこれをマクロを使用しないものに書き直すことを何度か試みました。

inline void DEBUG_LOG(LogLevel aLogLevel, const std::stringstream& aWhat) {
    ...
}

と...

template<typename WhatT> inline void DEBUG_LOG(LogLevel aLogLevel, WhatT aWhat) {
    ...  }

役に立たない(上記の2つの書き換えのいずれも、最初の例のロギングコードに対してコンパイルされません)。他のアイデアはありますか?これはできますか?それとも、マクロのままにしておくのが最善ですか?

4

3 に答える 3

6

__LINE__ロギングは、他の方法では利用できない呼び出しサイト情報( 、、__FILE__...)が必要なため、マクロを完全に排除できない数少ない場所の1つです。この質問も参照してください。

ただし、ロギングロジックを別の関数(またはオブジェクト)に移動し、マクロを介して呼び出しサイト情報のみを提供することはできます。このためのテンプレート関数も必要ありません。

#define DEBUG_LOG(Level, What) \
  isEnabled(Level) && scoped_logger(Level, __FILE__, __LINE__).stream() << What

これにより、使用法は同じままになります。これは、コードの負荷を変更する必要がないため、良い考えかもしれません。を使用する&&と、句を使用した場合と同じ短期間の動作が得られますif

これで、scoped_loggerは、破壊されたときに取得したものを実際にログに記録するRAIIオブジェクトになります。これは、デストラクタとも呼ばれます。

struct scoped_logger
{
  scoped_logger(LogLevel level, char const* file, unsigned line)
    : _level(level)
  { _ss << file << "(" << line << ") [" << getpid() << "] : "; }

  std::stringstream& stream(){ return _ss; }
  ~scoped_logger(){ logger::log(_level, _ss.str()); }
private:
  std::stringstream _ss;
  LogLevel _level;
};

基になるstd::stringstreamオブジェクトを公開することで、独自のオーバーロードを作成する手間を省くことができますoperator<<(これはばかげています)。関数を介して実際に公開する必要があります。scoped_loggerオブジェクトが一時的なもの(右辺値)の場合、メンバーも同様であり、何らかの方法で左辺値(参照)に変換しないとstd::stringstream、のメンバーのオーバーロードのみが検出されます。operator<<この問題の詳細については、こちらを参照してください(この問題は、右辺値ストリームインサーターを使用するC ++ 11で修正されていることに注意してください)。この「変換」は、ストリームへの通常の参照を返すだけのメンバー関数を呼び出すことによって行われます。

Ideoneの小さなライブ例。

于 2012-03-12T15:29:16.343 に答える
5

いいえ、マクロで演算子(<<)を使用しているため、この正確なマクロをテンプレートとして書き換えることはできません。演算子(<<)は、テンプレート引数または関数引数として渡すことはできません。

同じ問題があり、次のような構文を使用して、クラスベースのアプローチで解決しました。

DEBUG_LOG(TRACE_LOG_LEVEL) << "The X value = " << x << ", pointer = " << *x << logger::flush;

これは確かに(正規表現を使用して)コードを書き直し、クラスマジックを導入する必要がありますが、柔軟性が向上するという追加の利点があります(出力の遅延、ログレベルごとの出力オプション(ファイルまたはstdoutへ)など)。

于 2012-03-12T14:04:00.970 に答える
3

その特定のマクロを関数に変換する際の問題は、のようなもの"The X value = " << xが有効な式ではないということです。

<<演算子は左結合です。つまり、フォーム内の何かがA << B << Cとして扱われ(A << B) << Cます。iostreamのオーバーロードされた挿入演算子は、常に同じストリームへの参照を返すため、同じステートメントでより多くの挿入を行うことができます。つまり、がである場合、はを返すためA、と同じ効果があります。std::stringstreamA << BA(A << B) << C;A << B; A << C;

B << Cこれで、マクロに問題なく渡すことができます。マクロはそれをトークンの束として扱うだけであり、すべての置換が完了するまで、それらが何を意味するかについて心配する必要はありません。その時点で、左結合ルールを開始できます。ただし、関数の引数については、インライン化およびテンプレート化されている場合でも、コンパイラーは引数のタイプとその値を見つける方法を理解する必要があります。B << Cが無効な場合(Bストリームでも整数でもないため)、コンパイラエラー。が有効であってもB << C、関数パラメーターは常に呼び出された関数の何よりも先に評価されるため、最終的には動作A << (B << C)になりますが、これはここで必要な動作ではありません。

マクロのすべての使用法を変更したい場合(たとえば、<<トークンの代わりにコンマを使用するか、@ svenihoneyの提案のようなものを使用する)、何かを行う方法があります。そうでない場合、そのマクロは関数のように扱うことはできません。

ただし、このマクロを使用する必要のあるすべてのプログラマーが、で始まる行に、および/またはDEBUG_LOGに関連するコンパイラエラーが表示される理由を理解している限り、このマクロに害はないと思います。std::stringstreamlogger::log

マクロを保持している場合は、C ++ FAQの回答39.4および39.5で、このようなマクロが驚くようないくつかの厄介な方法を回避するための秘訣を確認してください。

于 2012-03-12T14:08:36.050 に答える