C++:函数包装器,其行为与函数本身一样

Fra*_*ank 15 c++ functional-programming function tr1 wrapper

我怎样才能编写一个可以包装任何函数的包装器,并且可以像函数本身一样调用它?

我需要这个的原因:我想要一个Timer对象,它可以包装一个函数,就像函数本身一样,加上它记录所有调用的累计时间.

场景看起来像这样:

// a function whose runtime should be logged
double foo(int x) {
  // do something that takes some time ...
}

Timer timed_foo(&foo); // timed_foo is a wrapping fct obj
double a = timed_foo(3);
double b = timed_foo(2);
double c = timed_foo(5);
std::cout << "Elapsed: " << timed_foo.GetElapsedTime();
Run Code Online (Sandbox Code Playgroud)

我怎么写这Timer堂课?

我正在尝试这样的事情:

#include <tr1/functional>
using std::tr1::function;

template<class Function>
class Timer {

public:

  Timer(Function& fct)
  : fct_(fct) {}

  ??? operator()(???){
    // call the fct_,   
    // measure runtime and add to elapsed_time_
  }

  long GetElapsedTime() { return elapsed_time_; }

private:
  Function& fct_;
  long elapsed_time_;
};

int main(int argc, char** argv){
    typedef function<double(int)> MyFct;
    MyFct fct = &foo;
    Timer<MyFct> timed_foo(fct);
    double a = timed_foo(3);
    double b = timed_foo(2);
    double c = timed_foo(5);
    std::cout << "Elapsed: " << timed_foo.GetElapsedTime();
}
Run Code Online (Sandbox Code Playgroud)

(顺便说一下,我知道gprof和其他用于分析运行时的工具,但是有这样一个Timer对象来记录一些选定函数的运行时对我来说更方便.)

Joh*_*itb 10

基本上,在当前的C++中,你想要做的事是不可能的.对于你想要包装的任何数量的函数,你需要重载

const reference
non-const reference
Run Code Online (Sandbox Code Playgroud)

但是它仍然没有完美转发(一些边缘案例仍然存在),但它应该合理地运作.如果你自己限制为const引用,你可以使用这个(未测试):

template<class Function>
class Timer {
    typedef typename boost::function_types
       ::result_type<Function>::type return_type;

public:

  Timer(Function fct)
  : fct_(fct) {}

// macro generating one overload
#define FN(Z, N, D) \
  BOOST_PP_EXPR_IF(N, template<BOOST_PP_ENUM_PARAMS(N, typename T)>) \
  return_type operator()(BOOST_PP_ENUM_BINARY_PARAMS(N, T, const& t)) { \
      /* some stuff here */ \
      fct_(ENUM_PARAMS(N, t)); \
  }

// generate overloads for up to 10 parameters
BOOST_PP_REPEAT(10, FN, ~)
#undef FN

  long GetElapsedTime() { return elapsed_time_; }

private:
  // void() -> void(*)()
  typename boost::decay<Function>::type fct_;
  long elapsed_time_;
};
Run Code Online (Sandbox Code Playgroud)

请注意,对于返回类型,您可以使用boost的函数类型库.然后

Timer<void(int)> t(&foo);
t(10);
Run Code Online (Sandbox Code Playgroud)

您也可以使用纯值参数重载,然后如果您想通过引用传递某些内容,请使用boost::ref.这实际上是一种非常常见的技术,特别是当这些参数将被保存时(这种技术也被用于boost::bind):

// if you want to have reference parameters:
void bar(int &i) { i = 10; }

Timer<void(int&)> f(&bar);
int a; 
f(boost::ref(a)); 
assert(a == 10);
Run Code Online (Sandbox Code Playgroud)

或者您可以为const和非const版本添加这些重载,如上所述.查看Boost.Preprocessor,了解如何编写正确的宏.

你应该知道,如果你想能够传递任意的callables(不仅仅是函数),整个事情会变得更加困难,因为你需要一种方法来获得他们的结果类型(这并不是那么容易).C++ 1x将使这种东西变得更容易.


Nic*_*kis 9

这是一个简单的函数包装方法.

template<typename T>
class Functor {
  T f;
public:
  Functor(T t){
      f = t;
  }
  T& operator()(){
    return f;
  }
};


int add(int a, int b)
{
  return a+b;
}

void testing()
{
  Functor<int (*)(int, int)> f(add);
  cout << f()(2,3);
}
Run Code Online (Sandbox Code Playgroud)


Myk*_*yev 6

我假设您需要将其用于测试目的,并且不会将它们用作真正的代理或装饰器.因此,您不需要使用operator(),并且可以使用任何其他更方便的调用方法.

template <typename TFunction>
class TimerWrapper
{
public:
    TimerWrapper(TFunction function, clock_t& elapsedTime):
        call(function),
        startTime_(::clock()),
        elapsedTime_(elapsedTime)
    {
    }

    ~TimerWrapper()
    {
        const clock_t endTime_ = ::clock();
        const clock_t diff = (endTime_ - startTime_);
        elapsedTime_ += diff;
    }

    TFunction call;
private:
    const clock_t startTime_;
    clock_t& elapsedTime_;
};


template <typename TFunction>
TimerWrapper<TFunction> test_time(TFunction function, clock_t& elapsedTime)
{
    return TimerWrapper<TFunction>(function, elapsedTime);
}
Run Code Online (Sandbox Code Playgroud)

因此,要测试一些功能,您应该只使用test_time功能而不是直接TimerWrapper结构

int test1()
{
    std::cout << "test1\n";
    return 0;
}

void test2(int parameter)
{
    std::cout << "test2 with parameter " << parameter << "\n";
}

int main()
{
    clock_t elapsedTime = 0;
    test_time(test1, elapsedTime).call();
    test_time(test2, elapsedTime).call(20);
    double result = test_time(sqrt, elapsedTime).call(9.0);

    std::cout << "result = " << result << std::endl;
    std::cout << elapsedTime << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)


a.l*_*ram 5

如果您查看包含的 std::tr1::function 的实现,您可能会找到答案。

在c++11中,std::函数是通过可变参数模板实现的。使用这样的模板,你的计时器类可以看起来像

template<typename>
class Timer;

template<typename R, typename... T>
class Timer<R(T...)>
{
    typedef R (*function_type)(T...);

    function_type function;
public:
    Timer(function_type f)
    {
        function = f;
    }

    R operator() (T&&... a)
    {
        // timer starts here
        R r = function(std::forward<T>(a)...);
        // timer ends here
        return r;
    }
};

float some_function(int x, double y)
{
    return static_cast<float>( static_cast<double>(x) * y );
}


Timer<float(int,double)> timed_function(some_function); // create a timed function

float r = timed_function(3,6.0); // call the timed function
Run Code Online (Sandbox Code Playgroud)