メンバ関数ではないロガー

あるプロジェクトでは、log4cppのラッパークラスがありました。 ある晴れた日、私はそのインターフェースが気に入らないと思い、少しやり直してから、もう少しやり直すことにしました。 次に、一般化プログラミングを使用してみませんか? そして、それは好転しました...しかし、ロギングの可変的な動作、つまり、選択されたタイプに応じて、画面またはファイルまたは他の場所への出力のみが必要でした。





適切なログオプションを選択することは、ポリシーを介して行うのが最適であるように思えました。

class LogFile class LogConsole class LogStream
      
      







ログクラスは、これらのポリシーのいずれかを継承します



 template<class LogType> class Logger : public <LogType>
      
      







ロガーの動作を決定するクラス自体



 class LogConsole { public: void do_log(log4cpp::LoggingEvent ev) { std::cout<<ev.timeStamp.getSeconds()<<" " <<log4cpp::Priority::getPriorityName(ev.priority)<<" " <<ev.categoryName<<" "<<ev.ndc<<": "<<ev.message<<std::endl; } };
      
      







このクラスは単にメッセージを表示します。 ここではLog4cppは実際には使用されていませんが、log4cpp :: LoggingEventのみが使用されています。



 class LogStream : SipAppender<std::ostringstream> { public: std::string do_buffer() const { return _w.str(); } void do_clear() { _w.str(std::string()); } void do_log(log4cpp::LoggingEvent ev) { appender.doAppend(ev); } };
      
      







クラスはostringstreamで動作し、do_buffer関数を呼び出すことにより、画面に何も表示せず、オンデマンドでメッセージを表示します。



 class LogFile : SipAppender<std::ofstream> { public: LogFile() { _w.open("SIP_LOG.txt"); } ~LogFile() { _w.close(); } void do_log(log4cpp::LoggingEvent ev) { appender.doAppend(ev); } };
      
      







クラスはファイルにイベントを追加します。そのようなロガーを作成するときは、ファイルを作成する必要があります。ロガーを破棄するとき、ファイルを閉じる必要があります。これらはすべて、コンストラクターとデストラクタでそれぞれ記述されます。

1つは、すべての関数名がdo_で始まることです。



クラスは実際にはlog4cppのラッパーなので、一部はAppenderから継承する必要があります。Appenderにはストリームストリームとこのストリームにイベントを追加するオブジェクトが含まれ、Appenderはこれらのオブジェクトを相続人に提供します。



 template<class Writer> struct SipAppender { Writer _w; log4cpp::OstreamAppender appender; SipAppender() : appender(log4cpp::OstreamAppender("logger", &_w)){} };
      
      







ロガークラスに直接なりました。 それをシングルトンにします。 最も簡単です。 さらに、c ++ 11はそのような機会を与えてくれます。



 template<class LogType> class Logger : public <LogType> { DEFAULT_FUNC(do_buffer) DEFAULT_FUNC(do_clear) Logger() = default; static Logger& instance() { static Logger theSingleInstance; return theSingleInstance; } void log(log4cpp::Priority::PriorityLevel p, const std::string &msg) { this->do_log(log4cpp::LoggingEvent("CATEGORY",msg,"NDC",p)); } public: static void debug(const std::string ¶m){ instance().log(log4cpp::Priority::DEBUG, param); } static void info(const std::string ¶m){ instance().log(log4cpp::Priority::INFO, param); } static void error(const std::string ¶m){ instance().log(log4cpp::Priority::ERROR, param); } static std::string buffer() { return _do_buffer<Logger>::_do(&instance(), [](){return std::string();}); } static void clear() { _do_clear<Logger>::_do(&instance(), []()->void{}); } Logger& operator=(const Logger&) = delete; };
      
      







ここでは、完全に機能するために必要なすべてのもの。 インスタンス関数は閉じていると宣言されています。なぜなら、最初の-ロガーオブジェクト自体へのアクセス権を与えたくないし、ロガーインスタンスを呼び出すときに書き込みたくないからです。

幸いなことに、インターフェースは小さく、すべての機能を静的にできます。



関数debug、info、errorを使用すると、すべてが明確になり、インスタンスを呼び出し、優先度とメッセージを記録します。

お気付きかもしれませんが、バッファおよびクリア関数には特定の異常があります。これはDEFAULT_FUNCマクロに関連付けられています。

原則として、バッファ(ログバッファの内容の出力)は、基本クラスのdo_bufferを呼び出す必要があります。 問題は、すべてのクラスに対応する機能があるわけではないことです。

別のクラスと対応する仮想関数を使用して問題を解決し、そこからポリシーを継承することはおそらく可能ですが、すべてのポリシークラスに追加のインターフェイスをドラッグしたくありませんでした。

さらに、機能が論理的に相互接続されていない場合、それらを1つのインターフェイスにプッシュするのは奇妙です。 何らかの方法で、クラス内の関数の存在の問題を解決する構造を定義するマクロを書くことが決定されました。



マクロ自体



 #define DEFAULT_FUNC(name) \ template<class T, class Enable = void> \ struct _##name \ { \ template<class DF> \ static auto _do(T *, DF df) -> decltype(df()) { return df(); } \ template<class DF> \ static auto _do(const T *, DF df) -> decltype(df()) { return df(); } \ }; \ template<class T> \ struct _##name <T, typename std::enable_if<std::is_member_function_pointer<decltype(&T::name)>::value>::type > \ { \ template<class DF> \ static auto _do(T *obj, DF df) -> decltype(df()) { (void)(df); return obj->name(); } \ template<class DF> \ static auto _do(const T *obj, DF df) -> decltype(df()) { (void)(df); return obj->name(); } \ };
      
      







ご覧のとおり、ここで_ ##構造体の名前はSFINAEを使用して定義されます(do_buffer関数の場合、構造体は_do_bufferと呼ばれます)。 。

クラスTの関数のメンバーシップは、std :: is_member_function_pointer <decltype(&T :: name)>によって決定されます。 魔法。

関数がクラスに属していない場合は、同じ_do関数で渡されるファンクターが実行されます。

オブジェクトTに定数が渡されると、関数はオーバーロードされます。 少し実験して、私はこのオプションで停止しました。



したがって、これにより、目的の関数がテンプレートの基本クラスに突然ない場合に、厄介なコンパイルエラーから保護できます。 また、突然出力方法を変更する必要がある場合に、ロギングアルゴリズムの互換性が確保されます。



たとえば、次のようなコードの場合:



 using TestType = LogConsole; int main() { Logger<TestType>::info("Start log"); Logger<TestType>::error("Middle log"); Logger<TestType>::debug("End log"); std::cout<<Logger<TestType>::buffer()<<std::endl; Logger<TestType>::clear(); std::cout<<"clear: "<<std::endl; std::cout<<Logger<TestType>::buffer()<<std::endl; return 0; }
      
      







どのTestTypeでも動作し続けることが保証されています。

また、これまでのところ、このようなコードは私に適していると言いますが、もっとエレガントな方法があるかもしれません。



UPD

読者の批判的な評価のおかげで、メソッドが完全ではなく、マクロがオーバーロードされた関数と引数の存在を考慮していないことが明らかになりました。 コードを少し変更しました。



引数なしの単純な関数の個別のマクロ

 #define D_FUNC_VOID(name) \ template<class S, class R, class DEnable = void> \ struct __##name \ { \ template<class DF> \ static R _do(S *, DF df) { return df(); } \ template<class DF> \ static R _do(const S *, DF df) { return df(); } \ }; \ template<class S, class R> \ struct __##name <S, R, typename std::enable_if<std::is_same<std::decltype(declval<S>().name()), R>::value>::type > \ { \ template<class DF> \ static R _do(S *obj, DF df) { (void)(df); return obj->name(); } \ template<class DF> \ static R _do(const S *obj, DF df) { (void)(df); return obj->name(); } \ };
      
      







引数を持つ関数の場合は個別

 #define D_FUNC_ARG(name) \ template<class S, class A, class R, class DEnable = void> \ struct __##name \ { \ template<class DF> \ static R _do(S *, A a, DF df) { return df(a); } \ template<class DF> \ static R _do(const S *, A a, DF df) { return df(a); } \ }; \ template<class S, class A, class R> \ struct __##name <S, A, R, typename std::enable_if<std::is_same<decltype(std::declval<S>().name(std::declval<A>())), R>::value>::type> \ { \ template<class DF> \ static R _do(S *obj, A a, DF df) { (void)(df); return obj->name(a); } \ template<class DF> \ static R _do(const S *obj, A a, DF df) { (void)(df); return obj->name(a); } \ };
      
      







呼び出す特定の関数を決定するための共通マクロ。

 #define D_FUNC(name) \ template<class T, class Arg = void, class Ret = void, class Enable = void> \ struct _##name \ { \ D_FUNC_ARG(name) \ template<class DF> \ static Ret _do(T *obj, Arg a, DF df) { return __##name<T, Arg, Ret>::_do(obj, a, df); } \ }; \ template <class T, class Arg, class Ret> \ struct _##name<T, Arg, Ret, typename std::enable_if<std::is_void<Arg>::value>::type> \ { \ D_FUNC_VOID(name) \ template<class DF> \ static Ret _do(T *obj, DF df) { (void)(df); return __##name<T, Ret>::_do(obj, df); } \ };
      
      







これにより、「保護」する必要があるすべての関数がD_FUNCマクロに置き換えられます。



その後、そのような挑戦が可能です。

 _do_log<Logger, log4cpp::LoggingEvent>::_do(&instance(), log4cpp::LoggingEvent("CATEGORY",msg,"NDC",p), [](log4cpp::LoggingEvent){}); _do_buffer<Logger, void, std::string>::_do(&instance(), [](std::string){ return std::string();});
      
      





はい、確かに、これはこの地獄を使用するための非常に簡単な例ですが、関心はそのような機会を実現することが可能であったかどうかでした。



UPD

最新の変更(D_FUNC)では、Loggerクラスは次のようになります

 template<class LogType> class Logger : public LogType { D_FUNC(do_clear) D_FUNC(do_buffer) D_FUNC(do_log) Logger() = default; static Logger& instance() { static Logger theSingleInstance; return theSingleInstance; } void log(log4cpp::Priority::PriorityLevel p, const std::string &msg) { _do_log<Logger, log4cpp::LoggingEvent>::_do(&instance(), log4cpp::LoggingEvent("CATEGORY",msg,"NDC",p), [](log4cpp::LoggingEvent){}); } public: static void debug(const std::string ¶m){ instance().log(log4cpp::Priority::DEBUG, param); } static void info(const std::string ¶m){ instance().log(log4cpp::Priority::INFO, param);} static void error(const std::string ¶m){ instance().log(log4cpp::Priority::ERROR, param);} static std::string buffer() { return _do_buffer<Logger, void, std::string>::_do(&instance(), [](){ return std::string();}); } static void clear() { _do_clear<Logger>::_do(&instance(), [](){}); } Logger& operator=(const Logger&) = delete; Logger(const Logger&) = delete; };
      
      






All Articles