Link to home
Start Free TrialLog in
Avatar of PMH4514
PMH4514

asked on

Function pointers and callbacks (I think!)

My question is with regards to callbacks and function pointers, two topics with which I have little experience but am trying to figure out.

Say I have class A , B and C

each class has a method Run()

classes B and C each contain and instance of A.

classes B and C do not derive from a common base class (while that might simplify things via polymorphism and virtual functions, it would not be appropriate from a modelling standpoint)

when A:Run() is invoked, it needs to to execute B:Run() or A:Run() depending on which class, A or B, is the container.  

(I know, this isn't the best OO design, contained classes shouldn't need to know of whom contains them)

Anyway - if C were not in the picture, I could just do this in A:

private:
  B*  m_pParent;

and then:
A::Run()
{
  m_pParent->Run();
}

But since there are two discrete types of classes (in this example) I can't just store a member pointer to the container object because I don't know of which type it should be defined as.

Isn't this kinda what function pointers are for? when B instantiates its contained instance of A, is there a way for B to tell A  -  "MY Run() method is what you will execute when your Run() is invoked, here is the address to it so you don't need to know if I am B or C" ?

If that is possible, how would I go about doing it?

Thanks!
Paul
Avatar of topdog770
topdog770
Flag of United States of America image

One option would be to create A::WhichFunctionToRun( someFuncPointer * p )
{
   if( p == null )
   {
        A::Run();
   }
   else
   {
      p();
   }
}

Avatar of Axter
Hi PMH4514,
> >If that is possible, how would I go about doing it?

To do this with a non-static member function, you would need to have an instance of the target object in order to call it's member function.

David Maisonave :-)
Cheers!
ASKER CERTIFIED SOLUTION
Avatar of Axter
Axter
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
If you don't like to use templates, heres a method using an abstract helper class.
Class A, B, and C do not have to be related using this method.
class FunctionHelper
{
public:
    virtual void Run()=0;
};

class A
{
public:
    void Run(FunctionHelper &f)
    {
        cout << "A::Run" << endl;
        f.Run();
    }
};

class B
{
public:
    void Run()
    {
        cout << "B::Run" << endl;
    }
    class FunctionHelper_T : public FunctionHelper
    {
    public:
        FunctionHelper_T(B& b):m_B(b){}
        void Run()
        {
            m_B.Run();
        }
        B& m_B;
    };
    void FunctionX()
    {
        m_A.Run(FunctionHelper_T(*this));
    }
    A m_A;
};

class C
{
public:
    void Run()
    {
        cout << "C::Run" << endl;
    }
    class FunctionHelper_T : public FunctionHelper
    {
    public:
        FunctionHelper_T(C& c):m_C(c){}
        void Run()
        {
            m_C.Run();
        }
        C& m_C;
    };
    void FunctionX()
    {
        m_A.Run(FunctionHelper_T(*this));
    }
    A m_A;
};

int main()
{
    B b;
    C c;
    b.FunctionX();
    c.FunctionX();
Possibly, you could make B and C include a class derived from A, but which takes a pointer or reference to the B/C parent. The derived class can call the run function in B or C as applicable, because it knows about B/C. Class A needs to call a virtual function to get to the derived class's run implementation. In the following example A's run calls the virtual function v_run, which is implemented in the derived class, which I've implemented as a template class.

--------8<--------
#include <iostream>
using std::cout;

class A {
      virtual void v_run() = 0; // Pure virtual function
public:
      void run() {
            cout << "A::run(): begin\n";
              v_run();
            cout << "A::run(): end\n";
      }
};

// Template class wraps itself around A and takes a reference to a parent, which is
// some class which is expected to have a public run method.
template<typename T> class D : public A {
      T& parent;
      void v_run() { // Overrides the PVF
            cout << "D::run(): begin\n";
              parent.run();
            cout << "D::f(): end\n";
      }
public:
      D(T& parent) : parent(parent) {}
};

class B {
      D<B> d;      // Contains D - derived from A but instantiated for C
public:
      B() : d(*this) {} // Compiler will probably warn you about using this
      void run() {cout << "B::run\n";}
      void f() {
            cout << "B::f(): begin\n";
              d.run();
            cout << "B::f(): end\n";
      }
};

class C {
      D<C> d;      // Contains D - derived from A but instantiated for D
public:
      C() : d(*this) {} // Compiler will probably warn you about using this
      void run() {cout << "C::run\n";}
      void f() {
            cout << "C::f(): begin\n";
              d.run();
            cout << "C::f(): end\n";
      }
};

int main()
{
      B b;
      b.f();
      cout << '\n';
      C c;
      c.f();
}
--------8<--------
I've only skimmed the previous answers, but don't think anyone's mentioned...

The boost library (boost.org) has a useful 'bind' template function which could help. You can do things like:


class A
{
  boost:function0<void> Callback;
.. and a constructor that takes a boost::function0<void> and assigns to callback;
.. then you'll call it via simply Callback() when you want to
}

then to pass into the constructor:

B BInstance;

new A( boost::function0<void> F = boost::bind( &B.Fire , BInstance ) )

(and similarly for C )

.. which is basically an implementaion of a helper wrapper as above.


Avatar of PMH4514
PMH4514

ASKER

wow, lots of good comments, I have to take a bit more time to review them all. Is this a design problem you guys encounter frequently? I took some time and was able to refactor a piece of my design and in fact found a base class that I hadn't thought of before that is appropriate to the design, and elliminates my need for this type of solution.. but it's interesting none-the-less, so keep the comments coming!
Avatar of PMH4514

ASKER

it seems to me that function pointers which cross class boundries, are a workaround to an improper or incomplete OO design. Would you agree?
>>are a workaround to an improper or incomplete OO design.  Would you agree?

No.
This is especially not so, when you don't have full control of all the objects original design.
For example, if you're using MFC, and you want to give a particular functionality to a CEdit derived class and a CComboBox derived class.
If you have full control of the MFC designed, then you could modify the base class (CWnd) and add the common functionality to that class.
However, in the real world, you don't always have access to the base class design, and therefore you need to develop workaround solutions that will allow separate classes to share the same logic.

The use of function pointers is just another way to produce polymorphism behavior.

FYI:
I recently had the exact above scenario, however I didn’t use function pointers, and instead I used a proxy class.
See following links:
http://code.axter.com/BrowseCtrl.h
http://code.axter.com/BrowseCtrl.cpp

In the code in the above link, the AssociatedButton class works like a proxy class.  And the derived classes like CEditBrowseFileCtrl and CComboBoxBrowseFileCtrl are able to pass their derived detail implementations to the parent BrowseCtrl class via the proxy class.


IMHO, a proxy class would be a better design for your requirements.
Avatar of PMH4514

ASKER

ahh gotcha..

I like the proxy idea. I should go back to my design patterns book and read up on it again.
Design Patterns is a good book, but I really hate their examples.

I've been thinking of creating an articale that would give good examples that are easy to understand for each pattern in the Design Patterns book.
Avatar of PMH4514

ASKER

You're talking about the Gamma one right? It's been a good reference for me for years.. I wish I had gotten into C++ earlier! I've always only been "sorta" able to implement some of the patterns in whatever language I was working with.


>>You're talking about the Gamma one right?
Yes (Erich Gamma)

Have you tried reading the descriptions for each pattern that's located in the inside jacket of the book?

It gives me a headache trying to determine the difference between one pattern and another using the description.

Maybe my comprehension is poor, but when I read the pattern description, I find it difficult to understand the difference between one pattern and another.


Thanks for the points
> It gives me a headache trying to determine the difference between one pattern and another using the description

You're not alone, David :-)