C ++: Оболочка функции, которая ведет себя так же, как сама функция - PullRequest
15 голосов
/ 18 мая 2009

Как я могу написать упаковщик, который может обернуть любую функцию и может быть вызван так же, как сама функция?

Причина, по которой мне это нужно: мне нужен объект 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();

Как я могу написать этот 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();
}

(Кстати, мне известны gprof и другие инструменты для профилирования времени выполнения, но наличие такого объекта Timer для регистрации времени выполнения нескольких выбранных функций более удобно для моих целей.)

Ответы [ 11 ]

10 голосов
/ 19 мая 2009

По сути, то, что вы хотите сделать, невозможно в текущем C ++. Для любого количества функций, которые вы хотите обернуть, вам нужно перегрузить на

const reference
non-const reference

Но тогда это все еще не идеально пересылка (некоторые крайние случаи все еще стоят), но это должно работать разумно хорошо. Если вы ограничиваете себя постоянными ссылками, вы можете пойти с этим (не проверено):

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_;
};

Обратите внимание, что для возвращаемого типа вы можете использовать библиотеку типов функций boost. Тогда

Timer<void(int)> t(&foo);
t(10);

Вы также можете перегрузить, используя параметры с чистыми значениями, а затем, если вы хотите передать что-либо по ссылке, используйте 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);

Или вы можете пойти и добавить эти перегрузки для константной и неконстантной версий, как описано выше. Посмотрите в Boost.Preprocessor , как написать правильные макросы.

Вы должны знать, что все это станет более сложным, если вы хотите иметь возможность передавать произвольные вызываемые объекты (не только функции), поскольку тогда вам понадобится способ получить их тип результата (это не так просто) , C ++ 1x упростит такие вещи.

9 голосов
/ 19 мая 2009

Вот простой способ обернуть функции.

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);
}
6 голосов
/ 19 мая 2009

Полагаю, вам это нужно для целей тестирования, и вы не собираетесь использовать их в качестве реальных прокси или декораторов. Таким образом, вам не нужно использовать 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);
}

Таким образом, чтобы протестировать некоторые из ваших функций, вы должны использовать только функцию 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;
}
2 голосов
/ 31 января 2017

Решение с использованием макросов и шаблонов: например, вы хотите обернуть

double foo( double i ) { printf("foo %f\n",i); return i; }
double r = WRAP( foo( 10.1 ) );

До и после вызова foo () должны вызываться функции-оболочки beginWrap () и endWrap (). (При этом функция endWrap () является шаблоном.)

void beginWrap() { printf("beginWrap()\n"); }
template <class T> T endWrap(const T& t) { printf("endWrap()\n"); return t; }

Макрос

#define WRAP(f) endWrap( (beginWrap(), f) );

использует приоритет оператора запятой, чтобы гарантировать, что beginWrap () вызывается первым. Результат f передается в endWrap (), которая просто возвращает его. Итак, вывод:

beginWrap()
foo 10.100000
endWrap()

И результат г содержит 10,1.

2 голосов
/ 29 мая 2013

Возможно, вы найдете ответ, если посмотрите на реализацию включенной функции std :: tr1 ::.

В c ++ 11 std :: function реализована с помощью шаблонов с переменным числом аргументов. Используя такие шаблоны, ваш класс таймера может выглядеть как

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
2 голосов
/ 19 мая 2009

Страуструп продемонстрировал умение обёртывания функций (инъекций) с перегрузкой operator->. Ключевая идея: operator-> будет повторяться до тех пор, пока не встретится собственный тип указателя, поэтому пусть Timer::operator-> возвращает временный объект, а временный объект возвращает свой указатель. Тогда произойдет следующее:

  1. temp obj создан (вызван ctor).
  2. вызвана целевая функция.
  3. temp obj destructed (вызван dtor).

И вы можете ввести любой код в ctor и dtor. Вот так.

template < class F >
class Holder {
public:
    Holder  (F v) : f(v) { std::cout << "Start!" << std::endl ; }
    ~Holder ()           { std::cout << "Stop!"  << std::endl ; }
    Holder* operator->() { return this ; }
    F f ;
} ;

template < class F >
class Timer {
public:
    Timer ( F v ) : f(v) {}
    Holder<F> operator->() { Holder<F> h(f) ; return h ; }
    F f ;
} ;

int foo ( int a, int b ) { std::cout << "foo()" << std::endl ; }

int main ()
{
    Timer<int(*)(int,int)> timer(foo) ;
    timer->f(1,2) ;
}

Как реализация, так и использование просты.

1 голос
/ 19 мая 2009

Если ваш компилятор поддерживает переменные макросы, я бы попробовал это:

class Timer {
  Timer();// when created notes start time
  ~ Timer();// when destroyed notes end time, computes elapsed time 
}

#define TIME_MACRO(fn, ...) { Timer t; fn(_VA_ARGS_); } 

Итак, чтобы использовать его, вы должны сделать следующее:

void test_me(int a, float b);

TIME_MACRO(test_me(a,b));

Это не так, и вам нужно поиграть, чтобы заставить возвращаемые типы работать (я думаю, вам нужно добавить имя типа в вызов TIME_MACRO, а затем сгенерировать временную переменную).

1 голос
/ 18 мая 2009

Вам предстоит сложная задача, если вы хотите создать универсальный класс, который может переносить и вызывать произвольную функцию. В этом случае вам нужно заставить функтор (operator ()) возвращать double и принимать в качестве параметра int. Затем вы создали семейство классов, которые могут вызывать все функции с той же сигнатурой. Как только вы захотите добавить больше типов функций, вам нужно больше функторов этой сигнатуры, например,

MyClass goo(double a, double b)
{
   // ..
}

template<class Function>
class Timer {

public:

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

  MyClass operator()(double a, double b){

  }

};

РЕДАКТИРОВАТЬ: Некоторые орфографические ошибки

1 голос
/ 18 мая 2009

Мне не совсем понятно, что вы ищете .. Однако для данного примера это просто:

void operator() (int x)
{
   clock_t start_time = ::clock();    // time before calling
   fct_(x);                           // call function
   clock_t end_time = ::clock();      // time when done

   elapsed_time_ += (end_time - start_time) / CLOCKS_PER_SEC;
}

Примечание: это будет измерять время в секундах. Если вы хотите использовать высокоточные таймеры, вам, вероятно, придется проверить определенные функции ОС (например, GetTickCount или QueryPerformanceCounter в Windows).

Если вы хотите иметь обертку для универсальной функции, вам следует взглянуть на Boost.Bind , который очень поможет.

0 голосов
/ 19 мая 2009

Вот как я бы это сделал, используя вместо шаблона указатель на функцию:

// pointer to a function of the form:   double foo(int x);
typedef double  (*MyFunc) (int);


// your function
double foo (int x) {
  // do something
  return 1.5 * x;
}


class Timer {
 public:

  Timer (MyFunc ptr)
    : m_ptr (ptr)
  { }

  double operator() (int x) {
    return m_ptr (x);
  }

 private:
  MyFunc m_ptr;
};

Я изменил его, чтобы не брать ссылку на функцию, а просто указатель на функцию. Использование остается прежним:

  Timer t(&foo);
  // call function directly
  foo(i);
  // call it through the wrapper
  t(i);
...