简体   繁体   中英

Passing pointer to member function to parent in c++

How can I make the following code work? I can't make the members static, Parent doesn't know about Child and I don't have access to boost. The reason I don't use virtual functions is that a Child class should be able to define 1-N handlers.

class Parent
{
public:
    void registerFileHandler(string ext, memFuncPtr);
};

class Child : public Parent
{
    Child()
    {
        registerFileHandler("jpg", &Child::loadJpg);
        registerFileHandler("png", &Child::loadPNG);
    }
    void loadJpg(string filename);
    void loadPNG(string filename);
};

EDIT: There were many answers. The ones that works best for me use the keywords erasure, std::bind and std::function which of course rely on c++11. Here is a full compilable example:

#include <string>
#include <map>
#include <iostream>
#include <functional>

using namespace std;

class Parent
{
public:
    void load(string filename)
    {
        // See if we can find a handler based on the extension.
        for(auto it = handlers.begin();it!=handlers.end();it++)
            if(filename.substr(filename.size()-it->first.size(), it->first.size())==it->first)
                it->second(filename);
    }
    template<typename Class>
    void registerFileHandler(Class* p, void (Class::*func)(string), string ext)
    {
        using namespace std::placeholders; //for _1, _2, _3...
        handlers[ext] = std::bind(func, p, _1);
    }
private:
    map<string, std::function<void(string)> > handlers;
};

class Child : public Parent
{
public:
    Child()
    {
        registerFileHandler(this, &Child::loadJpg, "jpg");
        registerFileHandler(this, &Child::loadPNG, "png");
    }
    void loadJpg(string filename)
    {
        cout << "loading the jpeg "<< filename << endl;
    }
    void loadPNG(string filename)
    {
        cout << "loading the png "<< filename << endl;
    }
};


int main(int argc, char* argv[])
{
    Child child;
    child.load("blah.jpg");
    child.load("blah.png");
    return 0;
}

You need some form of type erasure. Assuming you can't use any of the sophisticated ones that already exist ( boost::function , std::function ) then you can roll your own:

class MemFuncPtr {
    void *obj;
    void (*caller)(void*, string);
  public:
    MemFuncPtr(void *obj, void(*caller)(void*, string)) : obj(obj), caller(caller) {}
    void operator()(string filename) {
        caller(obj, filename);
    }
};

class Child : public Parent
{
    Child()
    {
        registerFileHandler("jpg", MemFuncPtr(this, &jpgcaller));
        registerFileHandler("png", MemFuncPtr(this, &pgncaller));
    }
    void loadJpg(string filename);
    void loadPNG(string filename);
  private:
    static void jpgcaller(void *obj, string filename) {
        static_cast<Child*>(obj)->loadJpg(filename);
    }
    static void pngcaller(void *obj, string filename) {
        static_cast<Child*>(obj)->loadPng(filename);
    }
};

I think you could get rid of those static functions using a function template with a pointer-to-member template parameter. But I'd probably make a mess of that code if I wrote it without testing it...

How about std::function and std::bind :

class Parent
{
public:

    void registerFileHandler(string ext, const std::function<void(string)> &f)
    {
    }
};

class Child : public Parent
{
public:
    Child()
    {
        using namespace std::placeholders; //for _1, _2, _3...
        registerFileHandler("jpg", std::bind(&Child::loadJpg, this, _1));
        registerFileHandler("png", std::bind(&Child::loadPNG, this, _1));
    }

    ... 

Lots of suggestions on how to work around passing in the function pointer - could you may be redesign slightly - and use inheritance properly..

class FileLoader
{
  virtual void load() = 0; // real load function
};

class LoadManager
{
  // Here is your registry
  std::map<std::string, std::uniqe_ptr<FileLoader>> _loaders;
};

class JpegLoader : public FileLoader
{
};

class BitmapLoader : public FileLoader
{
};

// etc.

// Now register these with the LoadManager and use from there...

Does this design not seem a little clearer? Obviously this suggestion is based on the simple snippet you've posted there, if your architecture is more complicated, then it's different story...

To make your code work, registerFileHandler() should pass a pointer to the actual child object that will load the image.

Probably better would be to define static Child Child::loadPNG(string filename) but it depends on what the Child and Parent objects really do.

class Child;  // unfortunately this is needed

class Parent
{
public:
    void registerFileHandler(string ext, void (Child::*mem_fn) (string), Child* obj) {
                                       // ^^^^the member func  ^^^^^^^^  //the object to be used 
         (obj->*mem_fn)("test");
    }
};


class Child : public Parent
{
    Child()
    {
        registerFileHandler("jpg", &Child::loadJpg, this);
        registerFileHandler("png", &Child::loadPNG, this);
                                                            // You need to pass this.
    }
    void loadJpg(string filename);
    void loadPNG(string filename);
};

However what you are doing is probably an overkill. Is there any reason why you can't use something like this?

class Parent
{
public:
    virtual void registerFileHandler(string ext)  = 0;
};


class Child : public Parent
{
    Child()
    {
        registerFileHandler("jpg");
        registerFileHandler("png");
    }
    void loadJpg(string filename);
    void loadPNG(string filename);

    virtual void registerFileHandler(string ext)   {
        loadJpg(ext);
        loadJpg(ext);
    }
};

Declaration-wise, you can get away with

class Parent
{
public:
    template <typename Child>
    void registerFileHandler(string ext, void (Child::*memFuncPtr)(string filename));
};

That doesn't solve the problem how to store those pointers, of course. One common solution is

struct Handler {
   virtual ~Handler() { };
  virtual void Load(Parent*, std::string) = 0;
};
template<typename Child>
struct ChildHandler {
  void (Child::*memFuncPtr)(string filename);
  virtual void Load(Parent* p, string filename) {
      dynamic_cast<Child&>(*p).*memFuncPtr(filename);
  }
};

This is of course yet another form of type erasure.

class Parent
{
public:
    // so we don't need to static-cast at the call site
    template <typename ChildT>
    void registerFileHandler(string ext, void (ChildT::*func)(string)) {
         doRegisterFileHandler(ext, static_cast<void (Parent::*func)(string)>(func));
    }
    void doRegisterFileHandler(string ext, void (Parent::*func)(string));
};

class Child : public Parent
{
    Child()
    {
        registerFileHandler("jpg", &Child::loadJpg);
        registerFileHandler("png", &Child::loadPNG);
    }
    void loadJpg(string filename);
    void loadPNG(string filename);
};

You call the function as usual:

this->*func(string)

in parent.

The specification says this should work (5.2.9/9 in C++03, 5.2.9/12 in C++11) and MFC uses it in the message map. Yet Microsoft Visual C++ is the compiler where it actually does not work in all cases. Because Microsoft came up with this "minimal representation of member pointers", where the pointer to member is represented differently depending on whether the class uses multiple inheritance or virtual inheritance or not. So if the child uses multiple inheritance, but parent does not, the cast cannot be done, because pointer to member of class with multiple inheritance does not fit into pointer to member of class with single inheritance. There is a compiler option to always use generic format, but code compiled with and without it is binary incompatible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM