С++: функциональная оболочка, которая ведет себя точно так же, как сама функция
Как я могу написать оболочку, которая может обернуть любую функцию и может быть вызвана так же, как сама функция?
Причина, по которой мне это нужно: я хочу, чтобы объект 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
для регистрации времени выполнения нескольких выбранных функций более удобен для моих целей.)
Ответы
Ответ 1
Вот простой способ обернуть функции.
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);
}
Ответ 2
В принципе, то, что вы хотите сделать, невозможно в текущем С++. Для любого количества функций, которые вы хотите обернуть, вам нужно перегрузить
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_;
};
Обратите внимание, что для типа возвращаемого значения вы можете использовать библиотеку типов ускоренных функций. Тогда
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, как описано выше. Посмотрите Boost.Preprocessor на то, как писать правильные макросы.
Вы должны знать, что все это станет более сложным, если вы захотите пройти произвольные вызовы (не только функции), так как вам понадобится способ получить их тип результата (что не все так просто), С++ 1x сделает этот способ проще.
Ответ 3
Я предполагаю, что вам нужно это для целей тестирования и не собираетесь использовать их в качестве реальных прокси или декораторов. Поэтому вам не нужно будет использовать 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;
}
Ответ 4
Stroustrup продемонстрировал навык обертки функции (вреда) с перегрузкой operator->
. Основная идея: operator->
будет повторяться до тех пор, пока не встретит собственный тип указателя, поэтому пусть Timer::operator->
возвращает объект temp, а объект temp возвращает свой указатель. Затем произойдет следующее:
- temp obj created (ctor called).
- целевая функция.
- 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) ;
}
Реализация и использование также легки.
Ответ 5
Мне не совсем понятно, для чего вы смотрите. Однако для данного примера это просто:
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, который будет чрезвычайно полезен.
Ответ 6
Вам предстоит большая задача, если вы хотите создать общий класс, который может обернуть и вызвать произвольную функцию. В этом случае вам нужно заставить функтор (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){
}
};
EDIT: некоторые орфографические ошибки
Ответ 7
Если ваш компилятор поддерживает переменные макросы, я бы попробовал следующее:
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, а затем создать его временную переменную).
Ответ 8
Возможно, вы найдете ответ, если вы посмотрите на реализацию функции std:: tr1::, которую вы включили.
В С++ 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
Ответ 9
В С++ функции являются гражданами первого класса, вы можете буквально передать функцию как значение.
Так как вы хотите, чтобы он взял int и вернул double:
Timer(double (*pt2Function)(int input)) {...
Ответ 10
Вот как бы я это сделал, используя указатель на функцию вместо шаблона:
// 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);
Ответ 11
Решение с использованием макросов и шаблонов: например, вы хотите обернуть
double foo( double i ) { printf("foo %f\n",i); return i; }
double r = WRAP( foo( 10.1 ) );
До и после вызова функции foo() должны быть вызваны функции wrapper 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.