How to write a class that may accept a function pointer and/or functor just like a smart pointer does for custom deleter?

StackOverflow https://stackoverflow.com/questions/20479350

Question

I'm trying to write a class that accepts a a function pointer AND/OR a functor to be user later by the class. To illustrate better what I'd like to do:

template <typename T> class Holder {
private:
  T *m_ptr;
  <something> m_func;

public:
  Holder(T *ptr) : m_ptr(ptr), m_func(NULL) {
  }

  Holder(T *ptr, <something> func) : m_ptr(ptr), m_func(func) {
  }

  ~Holder() {
    if (m_func) {
      m_func(m_ptr);
    } else {
      delete m_ptr;
    }
  }
};

Considering I'd like to handler objects of this type:

class MyClass {
public:
  void describe() {
    cout << "Bla bla bla ...";
  }
};

Then I could use it this way:

class MyClassFunctor {
public:
  void operator()(MyClass *ptr) const {
    cout << "Deleting ptr using functor: ";
    ptr->describe();
    cout << endl;
    delete ptr;
  }
};

int main() {
  MyClass *myclass = new MyClass();
  MyClassFunctor functor();
  {
    Holder<MyClass> holder(myClass, functor);
  }
  cout << "I'm out of context now!" << endl;
}

AND (not or) this way:

void myClassDeleter(MyClass *ptr) {
  cout << "Deleting ptr using function pointer: ";
  ptr->describe();
  cout << endl;
  delete ptr;
}

int main() {
  MyClass *myclass = new MyClass();
  {
    Holder<MyClass> holder(myClass, &myClassDeleter);
  }
  cout << "I'm out of context now!" << endl;
}

Notice I'd like to be able to use both approaches: Functors AND function pointers.

I'd say it is possible, since this is what Boost::shared ptr and tr1::shared_ptr does. I tried digging into Boost::shared_ptr code, but I couldn't really understand how they do it.

I'm sorry if my code is wrong or seems to be naive. I tried to explain the problem as concisely as possible, so code correctness wasn't my main focus here (I realize this is important).

Notice I don't even think about rewriting a smart pointer class from scratch. This is out of question here, since I know it is not a wise call. I'm interested in knowing how to do it so I can use this mechanism for other purposes. Smart pointers were simply the simplest use of that I could remember.

For now, I'd like to avoid using boost and C++11. Is it possible to do it using plain c++03?

Thanks very much for your time.

Was it helpful?

Solution

The answer is: Type Erasure.

The implementation is not that simple, and I suggest reading about Type Erasure a little (as I just did!).

First of all, you need to create the Type Erased apparatus:

class ActionBase {
    public:
        virtual ~ActionBase() { }
        virtual bool DoIt() = 0;
};

template<typename P>
class ActionP : public ActionBase {
    private:
        P *ptr;
    public:
        ActionP(P *p) : ptr(p) { }

        virtual bool DoIt() {
            cout << "Standard action (nothing to do)..." << endl;
            return true;
        }
};

template<typename P, class A>
class ActionPA : public ActionBase {
    private:
        P *ptr;
        A action;

    public:
        ActionPA(P *p, A & a ) : ptr(p), action(a) { }

        virtual bool DoIt() { return action(ptr); }
};

Then you can declare the Holder class:

template<typename T>
class Holder {
    private:
        // Avoid object copy and assignment.
        Holder(const Holder<T> &rhs);
        Holder<T>& operator=(const Holder<T> &rhs);

    protected:
        T* ptr;
        ActionBase *action;

    public:
        template<typename U> Holder(U *ptr) : ptr(ptr), action(new ActionP<U>(ptr)) { }

        template<typename U, class A> Holder(U* p, A a) : ptr(p), action(new ActionPA<U, A>(p, a)) { }

        virtual ~Holder() { delete ptr; delete action; }

        bool DoAction() {
            return this->action->DoIt();
        }
};

Then you can use it passing function pointers, functors, or even nothing:

template<typename T>
class ActionFunctor {
    public:
        bool operator()(T* instance) const {
            cout << "Action operator..." << endl;
            // Simple operation: set the value to 3 times the original value (works for int and string!!)
            instance->Set(instance->Get() + instance->Get());
            return true;
        }
};

template<typename T>
bool ActionFunc(T* instance) {
    cout << "Action function..." << endl;
    // Simple operation: set the value to 3 times the original value (works for int and string!!)
    instance->Set(instance->Get() + instance->Get() + instance->Get());
    return true;
}

int main() {
    {
        cout << "First test:" << endl;
        ActionFunctor<X> actionX;
        Holder<X> x1(new X(1), &ActionFunc<X>);
        Holder<X> x2(new X(10), actionX);
        Holder<X> x3(new X(100));
        x1.DoAction();
        x2.DoAction();
        x3.DoAction();
    }
    {
        cout << "Second test:" << endl;
        ActionFunctor<Y> actionY;
        Holder<Y> y1(new Y("A"), &ActionFunc<Y>);
        Holder<Y> y2(new Y("BB"), actionY);
        Holder<Y> y3(new Y("CCC"));
        y1.DoAction();
        y2.DoAction();
        y3.DoAction();
    }

    return 0;
}

Here is the output:

First test:
X constructor: 1
X constructor: 10
X constructor: 100
Action function...
Action operator...
Standard action (nothing to do)...
X desstructor: 100
X desstructor: 20
X desstructor: 3
Second test:
Y constructor: "A"
Y constructor: "BB"
Y constructor: "CCC"
Action function...
Action operator...
Standard action (nothing to do)...
Y destructor: "CCC" ...
Y destructor: "BBBB" ...
Y destructor: "AAA" ...

Hope it's useful for someone else.

OTHER TIPS

One obvious solution is to use boost::function or std::function. However, if you want to avoid the overhead these objects add, you can make Holder to accept a Callable as a template argument:

template <typename T, class F> 
class Holder 
{
private:
  T *m_ptr;
  F m_func;
//...

Of course, you'd have to make a helper function that would deduct the actual type of the Callable:

// depending on the nature of your functors, consider passing by const &
template<typename T, class F>
Holder<T, F> make_holder(T *t, F f)
{
  return Holder<T, F>(t, f);
}

Use it like this:

auto holder = make_holder(myClass, &myClassDeleter);
// or:
auto holder = make_holder(myClass, functor);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top