4

I have lots of instances of class X and each of them contain another class CB (sometimes more than one CB). The CB class has a function pointer in it that will be called after some event (click, timer, etc).

It would be so useful if I could just pass an instances member function to the local CB class instance (pass the say() function in this case) so that once CB triggers the function, it will modify things in its own instance only.

Example sample code:

 #include <iostream>                                                                                                     

 class CB {                                                                                                
     void (*function)(void);                                                                                             

     void init(void (*function)(void)) {                                                                                 
         this->function = function;                                                                                      
     }                                                                                                                   
 };                                                                                                                      

class X {                                                                                                               
    private:                                                                                                            
        CB cb;                                                                                                          

    public:                                                                                                             
        void init() {                                                                                                   
            cb.init( &(this->say) ); //<- Here's the problem                                                            
        }                                                                                                               

        void say() {                                                                                                    
            std::cout << "Hello World!" << std::endl;                                                                   
        }                                                                                                               
};                                                                                                                      

int main() {                                                                                                            
    X x;                                                                                                                
    x.init();                                                                                                           
} 

So my above example fails due to line 17 with the error:

no known conversion for argument 1 from ‘void (X::*)()’ to ‘void (*)()’

Is it possible to somehow pass an instances local say() function to the instances CB? The CB function can't be modified and unfortunately will be used by other classes of a different type.

I've read in other threads that this is most likely impossible unless the say() function is static or exists outside the class (which means it won't have access to an instances variables).

Is it possible to get this to work the way I have it? If not what would be the most common solutions (this seems like an issue a lot of people would run into)?

jadhachem
  • 1,123
  • 2
  • 11
  • 19
lotuspaperboy
  • 179
  • 1
  • 8

2 Answers2

1

Here's one way to get something at least vaguely similar to this, but still work:

#include <iostream>
#include <functional>

class CB
{
public:
    std::function<void(void)> function;

    template <class F>
    CB(F function) : function(function) { }

    void operator()() { function(); }
};

class X
{
private:
    CB cb;

public:
    X() :cb([this] {say(); }) { }

    void say() { std::cout << "Hello World!\n";  }

    void operator()() { cb(); }
};

int main() {
    X x;
    x();
}

Here I've rewritten CB a little bit. Some of that isn't compatible with existing code, but that's because the existing design is just not a good idea. Right now you're doing what's generally known as two-stage initialization, where you have to create an object, and call its init() before that object is really ready for use. This is generally frowned upon.

I've also changed CB to store an std::function, which can be used to store a pointer to a function, or almost anything else that can be invoked like a function.

I've then added an operator() to do the actual invocation of the stored function-like object.

In X I've made some changes as well, the biggest being using a lambda expression to produce an object that can be invoked like a function. This is passed to the embedded instance of CB during construction.

So the sequence is that the outside code creates an X object. That creates an embedded instance of the CB class, passing it a closure that invokes X::say. We then invoke X::operator(), which invokes CB::operator(), which invokes X::say().

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • It's unclear what value `class CB` is actually adding.... it appears that one could get rid of it and use `std::function cb;` – Ben Voigt May 21 '15 at 20:59
  • @BenVoigt: It may well be--I left it in, guessing that in real life it *might* also do more/other things as well. – Jerry Coffin May 21 '15 at 21:34
  • Tried this in 2021 and it works very well! I was only able to pass static methods as arguments to the constructor of another class before finding this answer. – spaL Nov 26 '21 at 10:43
1

"Is it possible to somehow pass an instances local say() function to the instances CB?

If you are sure you need a function pointer, use a std::function wrapper instead:

class CB {
    // void (*function)(void); Replace this with:
    std::function<void,void> fn_;
public:                                                                                  
    // void init(void (*function)(void)) { 
    //     this->function = function;
    // } Replace with a constructor
    CB(const std::function<void,void>& fn) : fn_(fn) {}
};

If you need it to bind it to a particular class instance use std::bind:

class X {
     CB cb_;
public:
    // Again use a constructor with a member initializer list
    void X() : cb_(std::bind(&X::say, this) {}

    void say() {
        std::cout << "Hello World!" << std::endl;
    }
};

"The CB function can't be modified and unfortunately will be used by other classes of a different type."
"I've read in other threads that this is most likely impossible unless the say() function is static or exists out side the class (which means it won't have access to an instances variables)."

If so, you're a bit lost somehow. Since the callback function signature, doesn't provide a kind of user data parameter, it's not possible to provide a wrapper callback provider holding a reference to a particular instance.

Does that callback function in your real code (I suppose you have simplified it in your question) really have a signature of void fn(void);, or does it provide some parameter, that allows to bind to any data instance like void fn(void* userData);?


You may still be able to provide a static member function of class X as callback. If that say() function doesn't change state of X (as in your sample), changing it to static isn't actually a problem (but I'm afraid that's not what you're after here):

class CB {
    void (*fn_)(void);
public:
    CB(void (*fn)(void)) : fn_(fn) {}
};

class X {
     CB cb_;
public:
    // Again use a constructor with a member initializer list
    void X() : cb_(&X::say) {}

    static void say() {
 // ^^^^^^
        std::cout << "Hello World!" << std::endl;
    }
};
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190