C++: envoltorio de funciones que se comporta como la función misma
function wrapper (11)
¿Cómo puedo escribir un contenedor que pueda envolver cualquier función y que se pueda llamar igual que la función en sí misma?
La razón por la que necesito esto es: quiero un objeto Timer que pueda envolver una función y comportarse como la función misma, además de que registra el tiempo acumulado de todas sus llamadas.
El escenario se vería así:
// 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();
¿Cómo puedo escribir esta clase de Timer
?
Estoy intentando algo como esto:
#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();
}
(Por cierto, sé de gprof
y otras herramientas para perfilar el tiempo de ejecución, pero tener un objeto Timer
para registrar el tiempo de ejecución de algunas funciones seleccionadas es más conveniente para mis propósitos).
Aquí hay una manera fácil de ajustar las funciones.
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);
}
Así es como lo haría, usando un puntero a función en lugar de una plantilla:
// 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;
};
Lo cambié para no tomar una referencia a la función, sino simplemente un puntero de función simple. El uso sigue siendo el mismo:
Timer t(&foo);
// call function directly
foo(i);
// call it through the wrapper
t(i);
Básicamente, lo que quieres hacer es imposible en C ++ actual. Para cualquier cantidad de función que desee envolver, debe sobrecargarla
const reference
non-const reference
Pero luego todavía no es perfectamente reenviante (algunos casos de borde todavía están en pie), pero debería funcionar razonablemente bien. Si se limita a las referencias de const, puede ir con este (no probado):
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_;
};
Tenga en cuenta que para el tipo de devolución, puede usar la biblioteca de tipos de funciones de boost. Entonces
Timer<void(int)> t(&foo);
t(10);
También puede sobrecargar usando parámetros de valor puro, y luego si quiere pasar algo por referencia, use boost::ref
. Esa es en realidad una técnica bastante común, especialmente cuando se van a guardar dichos parámetros (esta técnica también se usa para 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);
O puede ir y agregar esas sobrecargas para las versiones const y non const como se explicó anteriormente. Consulte Boost.Preprocessor para saber cómo escribir las macros adecuadas.
Debe tener en cuenta que todo se volverá más difícil si desea poder pasar callables arbitrarios (no solo funciones), ya que necesitará una forma de obtener su tipo de resultado (eso no es tan fácil). C ++ 1x hará que este tipo de cosas sea mucho más fácil.
En las funciones de C ++ son ciudadanos de primera clase, puede pasar literalmente una función como valor.
Como quieres que tome un int y devuelva un doble:
Timer(double (*pt2Function)(int input)) {...
Estás buscando un gran desafío si estás buscando crear una clase genérica que pueda envolver y llamar a una función arbitraria. En este caso, tendría que hacer que el funtor (el operador ()) devuelva el doble y tome un int como parámetro. Luego ha creado una familia de clases que puede llamar a todas las funciones con esa misma firma. Tan pronto como quiera agregar más tipos de funciones, necesita más funtores de esa firma, por ejemplo
MyClass goo(double a, double b)
{
// ..
}
template<class Function>
class Timer {
public:
Timer(Function& fct)
: fct_(fct) {}
MyClass operator()(double a, double b){
}
};
EDITAR: Algunos errores de ortografía
No es muy claro para mí lo que estás buscando. Sin embargo, para el ejemplo dado, es simplemente:
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;
}
Nota: Esto medirá el tiempo en segundos. Si desea tener temporizadores de alta precisión, probablemente tenga que verificar la funcionalidad específica del sistema operativo (como GetTickCount o QueryPerformanceCounter en Windows).
Si desea tener un contenedor de funciones genérico, debería echarle un vistazo a Boost.Bind que lo ayudará tremendamente.
Probablemente encuentre una respuesta si observa la implementación de std :: tr1 :: function que incluye.
En c ++ 11, std :: function se implementa con plantillas variadic. Usando tales plantillas tu clase de temporizador puede verse como
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
Si tu compilador admite macros variadic, intentaré esto:
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_); }
Entonces, para usarlo, harías esto:
void test_me(int a, float b);
TIME_MACRO(test_me(a,b));
Eso está fuera de lugar, y tendrías que jugar para que funcionen los tipos de devolución (creo que tendrías que agregar un nombre de tipo a la llamada TIME_MACRO y luego hacer que genere una variable de temperatura).
Stroustrup había demostrado una habilidad de envoltura de funciones (injaction) con la sobrecarga del operator->
. La idea clave es: operator->
se repetirá hasta que se encuentre con un tipo de puntero nativo, así que deje que Timer::operator->
devuelva un objeto temp y el objeto temp devuelva su puntero. Entonces sucederá lo siguiente:
- obj temporal creado (ctor llamado).
- función de destino llamada.
- obj templado destruido (dtor llamado).
Y puede inyectar cualquier código dentro del ctor y el dtor. Me gusta esto.
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) ;
}
La implementación y el uso son fáciles.
Supongo que lo necesita para fines de prueba y no los va a utilizar como proxies o decoradores reales. Por lo tanto, no necesitará usar operator () y podrá usar cualquier otro método de llamada más conveniente.
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);
}
Por lo tanto, para probar algunas de sus funciones, debe usar solo la función test_time
y no la estructura directa de 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;
}
Una solución que usa macros y plantillas: por ejemplo, desea envolver
double foo( double i ) { printf("foo %f/n",i); return i; }
double r = WRAP( foo( 10.1 ) );
Antes y después de llamar a foo () se deben llamar las funciones de envoltura beginWrap () y endWrap (). (Con endWrap () es una función de plantilla).
void beginWrap() { printf("beginWrap()/n"); }
template <class T> T endWrap(const T& t) { printf("endWrap()/n"); return t; }
La macro
#define WRAP(f) endWrap( (beginWrap(), f) );
usa la precedencia del operador de coma para asegurar que beginWrap () se llame primero. El resultado de f se pasa a endWrap () que simplemente lo devuelve. Entonces la salida es:
beginWrap()
foo 10.100000
endWrap()
Y el resultado r contiene 10.1.