3

このロギング テンプレート ファンクターを取得しました

template<typename RetType, typename Arg1Type, typename Class>

class Logger
{  public:

RetType operator()(Arg1Type s, ...)
{
    if(func != 0 && parser != 0)
        return (parser->*func)(s);
    else if(nfunc != 0)
        return nfunc(s);
    return RetType();
}

Logger& operator=(RetType(*fun)(Arg1Type s, ...))
{
    func = fun;
    return *this;
}

void Bind(Class* pars, RetType(Class::*fun)(Arg1Type s,...))
{
    parser = pars;
    func = fun;
    nfunc = 0;
}

void Bind(RetType(*fun)(Arg1Type s,...))
{
    nfunc = fun;
    func = 0;
    parser = 0;
}

private:
    RetType (Class::*func)(Arg1Type s, ...); //member class method
    RetType(*nfunc)(Arg1Type s, ...);        //non-member class method
    Class* parser;
};

これで、次のようなものを使用してこのクラスを呼び出すことができます:

Logger<int, const char*, WinLogger > p1;
WinLogger w1;
p1.Bind(&w1, &WinParser::Log);
p1("log");

しかし、次を使用して非メンバー関数にバインドしたい場合:

Logger<int, const char*, void> 

コンパイラは、「クラス」: の後に「::」が続く場合、クラスまたは名前空間でなければなりません。void 型を最初の Bind メソッドに合わせることができないためです。しかし、任意の DummyClass でロガーを作成すれば問題ありません。

Logger<int, const char*, DummyClass> p2;
p2.Bind(printf);
p2("printf called");

これは非常に醜いです。これの回避策はありますか?

おそらくboost::functionなどを使用する必要があることはわかっていますが、関数ポインタとファンクタがどのように機能するかを正確に学びたかったので、使用しないことにしました。

4

4 に答える 4

2

Logger<int, const char*>任意のクラスのメンバー関数または非メンバー関数のいずれかを呼び出す単一の型を定義することができます。これを行うには、Classパラメータを削除し、代わりに不透明オブジェクト ポインタ [ ] と不透明オブジェクトを受け入れる関数ポインタ[ ] をLogger格納する必要があります。void*R (*func)(void* object, A a)

Loggerこれにより、質問に含まれる機能の種類がわからなくなるため、質問の問題が解決されます。それが非メンバー、クラスXのメンバー、またはクラスのメンバーであるかどうかY

これは、私が C++03 用に開発した手法を使用して実装できます。この手法では、ラッパー関数 (別名「サンク」) を生成して、コンパイル時に既知の関数ポインターを介してメンバー関数と非メンバー関数を呼び出します。std::functionこれは、C++11のカットダウン特殊バージョンまたはC# のデリゲートと考えることができます。

template<typename F>
struct FunctionTraits;

template<typename R, typename C, typename A>
struct FunctionTraits<R (C::*)(A)> // matches a pointer to member function
{
    typedef R RetType;
    typedef C Class;
    typedef A Arg1Type;
};

template<typename R, typename A>
struct FunctionTraits<R (*)(A)> // matches a pointer to function
{
    typedef R RetType;
    typedef A Arg1Type;
};

template<typename RetType, typename Arg1Type>
class Logger
{
    typedef RetType(*Func)(void*, Arg1Type);
public:

    Logger(void* pars, Func func) : pars(pars), func(func)
    {
    }

    RetType operator()(Arg1Type a) const
    {
        // call the function with the opaque object
        return func(pars, a);
    }

private:

    Func func; // a pointer to a function accepting an opaque object
    void* pars; // a pointer to an opaque object
};

template<typename F, F p>
typename FunctionTraits<F>::RetType callMember(void* c, typename FunctionTraits<F>::Arg1Type a)
{
    // restore the type of the object
    return (static_cast<typename FunctionTraits<F>::Class*>(c)->*p)(a);
}

template<typename F, F p>
Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
    makeLogger(typename FunctionTraits<F>::Class* pars)
{
    typedef typename FunctionTraits<F>::RetType RetType;
    typedef typename FunctionTraits<F>::Arg1Type Arg1Type;
    // generates a 'thunk' function which calls the member 'p'
    return Logger<RetType, Arg1Type>(pars, &callMember<F, p>);
}

template<typename F, F p>
typename FunctionTraits<F>::RetType callNonMember(void*, typename FunctionTraits<F>::Arg1Type a)
{
    // the first parameter is not used
    return (p)(a);
}

template<typename F, F p>
Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
    makeLogger()
{
    typedef typename FunctionTraits<F>::RetType RetType;
    typedef typename FunctionTraits<F>::Arg1Type Arg1Type;
    // generates a 'thunk' function which calls the non-member 'p'
    return Logger<RetType, Arg1Type>(0, &callNonMember<F, p>);
}

int log(const char*);

struct Parser
{
    int log(const char*);
};

struct OtherParser
{
    int log(const char*);
};

int main()
{
    Logger<int, const char*> nonmember = makeLogger<decltype(&log), &log>();
    int result1 = nonmember("nonmember"); // calls log("nonmember");

    Parser pars;
    Logger<int, const char*> member = makeLogger<decltype(&Parser::log), &Parser::log>(&pars);
    int result2 = member("member"); // calls pars.log("member");

    OtherParser other;
    Logger<int, const char*> member2 = makeLogger<decltype(&OtherParser::log), &OtherParser::log>(&other);
    int result3 = member2("member2"); // calls other.log("member2");
}

を使用しているにもかかわらずvoid*、この手法はタイプ セーフであり、標準に準拠しています。

とは対照的にstd::function、生成された関数は、コンパイル時にポインターが認識されるため、メンバー/非メンバー ポインターを介して呼び出しをインライン化できます。

編集: 上記の例では、C++11 の decltype を使用して関数ポインターの型を自動的に決定していますが、これは必須ではありません。同じことを実現する C++98 互換の手法を提供できます。

template<typename F>
struct NonMemberHelper
{
    template<F p>
    static Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
        apply()
    {
        return makeLogger<F, p>();
    }
};

template<typename F>
NonMemberHelper<F> makeNonMemberHelper(F)
{
    return NonMemberHelper<F>();
}

template<typename F>
struct MemberHelper
{
    template<F p>
    static Logger<typename FunctionTraits<F>::RetType, typename FunctionTraits<F>::Arg1Type>
        apply(typename FunctionTraits<F>::Class* pars)
    {
        return makeLogger<F, p>(pars);
    }
};

template<typename F>
MemberHelper<F> makeMemberHelper(F)
{
    return MemberHelper<F>();
}

#define MAKE_LOGGER_NONMEMBER(func) makeNonMemberHelper(func).apply<(func)>()
#define MAKE_LOGGER(func, pars) makeMemberHelper(func).apply<(func)>(pars)

int main()
{
    Logger<int, const char*> callNonMember = MAKE_LOGGER_NONMEMBER(&log);
    int result1 = callNonMember("nonmember"); // calls log("nonmember");

    Parser pars;
    Logger<int, const char*> callMember = MAKE_LOGGER(&Parser::log, &pars);
    int result2 = callMember("member"); // calls pars.log("member");
}
于 2013-06-21T14:23:41.593 に答える
1
#include <iostream>
#include <type_traits>

template <typename T>
struct Logger
{
    Logger(T func) : func(func)
    {
    }

    template <typename... Args>
    auto operator()(const Args&... params) -> decltype(std::declval<T>()(params...))
    {
        return func(params...);
    }

    T func;
};

void simple_logger(const char *mesg)
{
    std::cout << "Simple: " << mesg << std::endl;
}

struct ComplexLogger
{
    std::ostream& operator()(const char *mesg)
    {
        return std::cout << "Complex: " << mesg;
    }
};

int main()
{
    Logger<decltype(&simple_logger)> l1(simple_logger);
    l1("hello!");
    ComplexLogger cl; 
    Logger<ComplexLogger> l2(cl);
    l2("hello!") << "yello!";
}

出力

Simple: hello!
Complex: hello!yello!

<algorithm>コンパレーター関数 ( BinaryPredicateUnaryPredicateなど) がテンプレート化された引数として使用されるため、ファンクター ( などless) または関数ポインターの両方を引数として渡すことができます。ここでも同様のトリックを使用しました。

それがあなたのケースに正確に一致するかどうかはわかりません(ロギングクラスがoperator()定義されていると予想され、これもC++ 11機能を使用しているため)、役立つかもしれないと思いました.

于 2013-06-20T17:51:43.003 に答える
0

クラスには、何らかのデフォルトの実装が必ず必要になります。しかし、幸いなことに、これはロガー クラス自体によって予知できるということです。交換するだけ

template<typename RetType, typename Arg1Type, typename Class>

template<typename RetType, typename Arg1Type, typename Class=DefaultDummyClass>

もちろん、DefaultDummyClass を予測する必要がありますが、ロガー クラスを使用したい人が提供する必要はありません。

于 2013-06-20T16:44:53.547 に答える