Fomalhaut
Fomalhaut

Reputation: 9765

Decorator for a class method

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

Answers (3)

AndyG
AndyG

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;

Live Demo


Breakdown

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 example
  • void(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.

Conclusion

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

R Sahu
R Sahu

Reputation: 206647

  1. Change measure to a function template to allow you to use any callable, not just a function.

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

Mike Lischke
Mike Lischke

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:

  1. Make m() static.
  2. Add a static member to MyClass that takes a reference to the current MyClass instance that is running measure. You must set this everytime before you call m(). Also, think about thread safetiness.
  3. In 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

Related Questions