Reputation: 9765
Supposing I have a function (a decorator) that measures the duration of given function:
#include <unistd.h>
void measure(void (*f)()) {
time_t tBegin = time(NULL);
f();
time_t tEnd = time(NULL);
cout << "Duration: " << (tEnd - tBegin) << " sec" << endl;
}
And I want to measure the duration of a method of a class. For example:
class Myclass {
private:
double _d;
public:
Myclass(double d) : _d(d) {}
void run() {
measure(m);
}
void m() const {
usleep(1000000 * _d);
}
};
int main() {
Myclass obj(2.0);
obj.run();
return 0;
}
Such implementation leads to the error:
error: invalid use of non-static member function
Is there a way in C++ to implement it correctly? It's supposed not to modify the external function measure
and the measured method is exactly non-static (it uses data of the instance). The measurement should be inside of the method run
.
I need solution for C++ 1998/2003 Standard.
Upvotes: 7
Views: 1600
Reputation: 41110
Since you mentioned you're stuck with C++03 the answer is that without changing the signature of method
, you're stuck with some static
tomfoolery:
The gist of what I'm about to do is to mimic a C++11 style lambda for your specific use case (wrapping a call to a const
member function with empty parameter list that returns void
). You could do some work to make this code a little more general.
First I'll post the wrapper code, then I'll break it down:
template<class T, void(T::*PTR)()const, size_t I>
struct bind_member
{
typedef void(*fn_type)();
explicit bind_member(const T* _ptr)
{
ptr = _ptr;
}
static void func(void)
{
(ptr->*PTR)();
}
operator fn_type()
{
return &func;
}
private:
static const T* ptr;
};
template<class T, void(T::*PTR)()const, size_t I>
const T* bind_member<T, PTR, I>::ptr = NULL;
The template arguments on our bind_member
struct (template<class T, void(T::*PTR)()const, size_t I>
) are for
T
is the type of class whose member function pointer we're wrapping. This becomes Myclass
in your examplevoid(T::*PTR)()const
is the ugly syntax for a const member function pointer on T
. The name of this template argument is PTR
size_t I
is included so that you can have multiple instances of your Myclass
use the same lambda to wrap the same function, only that you'll need to give each instance its own unique id to avoid them stepping on each other's toes. (In our example we only have one instance, so I gave it 0
instead of making some complicated static counter/member id combination).Within bind_member
we have our constructor that accepts a pointer to an instance of a class. This is solely for setting the static
class member.
Next we have func
, which is the wrapped way we will call Myclass::m
. If m
accepted an int
, then so would func
. Again, you could do some fancy templating to make this all a little more generic.
Now here's the trick:
We provide an implicit conversion of our bind_member
struct to a C-style function pointer, and we do so in the form of
typedef void(*fn_type)();
operator fn_type()
{
return &func;
}
Which works because our func
type is static
so it doesn't require an instance of the class and therefore does not appear as a member function pointer. The typedef
is for convenience, it's specifying the type of func
, which is that it accepts no arguments and returns void
.
Now onto actually calling within Myclass
:
void run()
{
bind_member<Myclass, &Myclass::m, 0> b(this);
measure(b);
}
I provide the template arguments as described above to create an instance of bind_member
, passing this
as an argument so that bind_member
can call the passed function (m
). I can pass &Myclass::m
as a non-type template parameter because function pointers are pointer types, which are integral types, meaning they have addresses, so we can use template parameters to get those addresses.
Finally, we can pass b
to our measure
function and we're done.
Upgrade to C++11 (or above) if you can, and write a lambda like some of the other answers here describe. Otherwise, beg to change the signature of measure
to be templated on a callable, and then we can wrap Myclass::m
a little easier. If all else fails, use the above approach.
Upvotes: 2
Reputation: 206647
Change measure
to a function template to allow you to use any callable, not just a function.
Use a lambda function in run
.
#include <iostream>
#include <time.h>
#include <unistd.h>
template <typename F>
void measure(F f) {
time_t tBegin = time(NULL);
f();
time_t tEnd = time(NULL);
std::cout << "Duration: " << (tEnd - tBegin) << " sec" << std::endl;
}
class Myclass {
private:
double _d;
public:
Myclass(double d) : _d(d) {}
void run() {
measure([=](){m();});
}
void m() const {
usleep(1000000 * _d);
}
};
int main() {
Myclass obj(2.0);
obj.run();
return 0;
}
Since, you are not allowed to modify measure
, you can use a helper class template and a function template to make it possible for you to use any callable.
#include <iostream>
#include <time.h>
#include <unistd.h>
void measure(void (*f)()) {
time_t tBegin = time(NULL);
f();
time_t tEnd = time(NULL);
std::cout << "Duration: " << (tEnd - tBegin) << " sec" << std::endl;
}
template <typename F>
struct MeasureFunctor
{
static F* f_;
static void run(){(*f_)();}
};
template <typename F> F* MeasureFunctor<F>::f_ = nullptr;
template <typename F>
void measure(F f) {
MeasureFunctor<F>::f_ = &f;
measure(MeasureFunctor<F>::run);
}
class Myclass {
private:
double _d;
public:
Myclass(double d) : _d(d) {}
void run() {
measure([=](){m();});
}
void m() const {
usleep(1000000 * _d);
}
};
int main() {
Myclass obj(2.0);
obj.run();
return 0;
}
Upvotes: 3
Reputation: 53367
I also would prefer a lambda (and a std::function as parameter for measure), but since you cannot change that, what about this idea:
m()
static.measure
. You must set this everytime before you call m()
. Also, think about thread safetiness.m()
you can then use this reference to get to _d
. Alternatively, you could even store the value of _d
in a static member var to have it available in m()
. Depends on what you actually need from MyClass
.This approach would allow to measure only one invocation at a time. For parallel execution of multiple measure()
calls you could use thread-local-storage for the reference you set in step 2.
Upvotes: 0