C++: оболочка функций, которая ведет себя так же, как сама функция

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

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

сценарий будет выглядеть так:

// 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();
}

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

11 ответов


здесь легко способ обернуть функции.

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

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

const reference
non-const reference

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

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

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

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


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

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

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

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


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

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()

и результат r содержит 10.1.


вам предстоит большая задача, если вы хотите создать общий класс, который может обернуть и вызвать произвольную функцию. В этом случае вам нужно будет сделать функтор (оператор ()), чтобы вернуть 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){

  }

};

EDIT: некоторые орфографии ошибки


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

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).

Если вы хотите иметь общую оболочку функции, вы должны посмотреть на импульс.Bind что поможет tremendeously.


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

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, а затем создать временную переменную).


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

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

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

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

// 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);

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

Так как вы хотите, чтобы он взял int и вернул double:

Timer(double (*pt2Function)(int input)) {...