1

C++ has limited ability to use pointer-to-member functions. I need something that will allow me to dynamically choose a callback member function, in order to use the Visitor pattern of the XMLNode::Accept(XMLVisitor *visitor) method from the TinyXML2 library.

To use XMLNode::Accept(), I must call it with a class which implements the XMLVisitor interface. Hence:

typedef bool (*Callback)(string, string);

class MyVisitor : public tinyxml2::XMLVisitor {
public:
    bool VisitExit(const tinyxml2::XMLElement &e) {
        callback(e.Name(), e.GetText());
    }
    Callback callback;
}

This works fine if my caller is NOT an object which wants to use one of its own methods as a callback function (so that it can access class variables). For example, this works:

bool myCallBackFunc(string e, string v) {
    cout << "Element " << e << " has value " << v << endl;
    return true;
}

int main(...) {
    tinyxml2::XMLDocument doc;
    doc.LoadFile("somefile.xml");
    MyVisitor visit;
    visit.callback = myCallBackFunc;
    doc.Accept(&visit);
}

However, in my use case, the parsing is done inside a method in a class. I have multiple applications which have similar but unique such classes. I'd like to use only one generic MyVisitor class, rather than have the visitor class have unique knowledge of the internals of each class which will call it.

Thus, it would be convenient if the callback function were a method in each calling class so that I can affect the internal state of the object instantiated from that calling class.

Top level: I have 5 server applications which talk to 5 different trading partners, who all send XML responses, but each is enough different that each server app has a class which is unique to that trading partner. I'm trying to follow good OO and DRY design, and avoid extra classes having unique knowledge while still doing basically the same work.

Here's the class method I want Accept() to call back.

ServiceClass::changeState(string elem, string value) {
   // Logic which sets member vars based on element found and its value.
}

Here's the class method which will call Accept() to walk the XML:

ServiceClass::processResponse(string xml) {
    // Parse XML and do something only if certain elements present.

    tinyxml2::XMLDocument doc;
    doc.Parse(xml.c_str(), xml.length());

    MyVisitor visit;
    visit.callback = &changeState; // ERROR.  Does not work.
    visit.callback = &ServiceClass::changeState; // ERROR.  Does not work.
    doc.Accept(&visit);
}

What's a simple way to get what I want? I can imagine more classes with derived classes unique to each situation, but that seems extremely verbose and clumsy.

Note: In the interest of brevity, my sample code above has no error checking, no null checking and may even have minor errors (e.g. treating const char * as a string ;-).

CXJ
  • 4,301
  • 3
  • 32
  • 62

4 Answers4

4

Below is the std::bind(..) example for what you're trying to do in C++11. For earlier C++ versions you could use the boost::bind utilities.

Fix your MyVisitor::VisitExit(...) method to return a boolean, by the way.

The code is converting const char * to std::string. tinyxml2 does not guarantee that the char * arguments from Name() or GetText() are not null. In fact in my experience they will be null at some point. You should guard against this. For the sake of not modifying your example too much I've not protected against this possibility everywhere in the example.

typedef bool(*Callback)(string, string);
using namespace std;
class MyVisitor : public tinyxml2::XMLVisitor {
public:
    bool VisitExit(const tinyxml2::XMLElement &e) {
    //  return callback(e.Name(), e.GetText());
        return true;
    }
    Callback callback;
};


/** Typedef to hopefully save on confusing syntax later */
typedef std::function< bool(const char * element_name, const char * element_text) > visitor_fn;

class MyBoundVisitor : public tinyxml2::XMLVisitor {
public:
    MyBoundVisitor(visitor_fn fn) : callback(fn) {}

    bool VisitExit(const tinyxml2::XMLElement &e) {
        return callback(e.Name() == nullptr ? "\0" : e.Name(), e.GetText() == nullptr ? "\0": e.GetText());
    }
    visitor_fn callback;
};

bool 
myCallBackFunc(string e, string v) {
    cout << "Element " << e << " has value " << v << endl;
    return true;
} 

int 
main()
{
        tinyxml2::XMLDocument doc;
        doc.LoadFile("somefile.xml");
        MyVisitor visit;
        visit.callback = myCallBackFunc;
        doc.Accept(&visit);

        visitor_fn fn = myCallBackFunc; // copy your function pointer into the std::function<> type
        MyBoundVisitor visit2(fn); // note: declare this outside the Accept(..) , do not use a temporary
        doc.Accept(&visit2);
}

So from within the ServiceClass method you'd do:

ServiceClass::processResponse(string xml) {
    // Parse XML and do something only if certain elements present.

    tinyxml2::XMLDocument doc;
    doc.Parse(xml.c_str(), xml.length());
// presuming changeState(const char *, const char *) here
    visitor_fn fn = std::bind(&ServiceClass::changeState,this,std::placeholders::_1,std::placeholders::_2);
    MyBoundVisitor visit2(fn); // the method pointer is in the fn argument, together with the instance (*this) it is a method for.
    doc.Accept(&visit);
}
JimmyNJ
  • 1,134
  • 1
  • 8
  • 23
  • No, I'm not actually blindly converting const char * to std::string. Likewise I removed all error and null checks. I was trying to make my code examples as small as possible but still illustrate the problem. I've not used std::bind before and will give it a look. – CXJ Dec 16 '16 at 20:02
  • Is this a typo? `visitor_fn fn = std::string(&ServiceClass:`? Shouldn't it be `visitor_fn fn = std::bind(&ServiceClass:` instead? – CXJ Dec 16 '16 at 22:48
  • This solution appears to be working correctly in my small test and in my initial server process testing. Thanks much! – CXJ Dec 17 '16 at 01:01
0

You can use generics in order to support whichever callback you'd like.

I've tried to mock the classes of the library in order to give you a fully runnable example:

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

class XmlNode {
public:
    XmlNode(const std::string& n, const std::string t) : name(n), txt(t) {}

    const std::string& Name() const { return name; }
    const std::string& GetText() const { return txt; }

private:
    std::string name;
    std::string txt;
};

class XMLVisitor {
public:
    virtual void VisitExit(const XmlNode& node) = 0;
    virtual ~XMLVisitor() {}
};

template<typename T>
class MyVisitor : XMLVisitor {
public:
    MyVisitor() {}

    void myInnerPrint(const XmlNode& node) {
        std::cout << "MyVisitor::myInnerPrint" << std::endl;
        std::cout << "node.Name(): " << node.Name() << std::endl;
        std::cout << "node.GetText(): " << node.GetText() << std::endl;
    }

    void SetCallback(T newCallback) {
        callback = newCallback;
    }

    virtual void VisitExit(const XmlNode& node) {
        callback(node);
    }

    T callback;
};

int main() {
    XmlNode node("In", "Member");
    MyVisitor<std::function<void(const XmlNode&)>> myVisitor;
    auto boundCall =
        [&myVisitor](const XmlNode& node) -> void {
        myVisitor.myInnerPrint(node);
    };

    myVisitor.SetCallback(boundCall);
    myVisitor.VisitExit(node);
    return 0;
}
Ynon
  • 334
  • 2
  • 7
0

First define a template and a helper function:

namespace detail {
    template<typename F>
    struct xml_visitor : tinyxml2::XMLVisitor {

        xml_visitor(F&& f) : f_(std::move(f)) {}

        virtual void VisitExit(const tinyxml2::XMLElement &e) {
            f_(e);
        }
    private:
        F f_;
    };
}

template<class F>
auto make_xml_visitor(F&& f)
{
    return detail::xml_visitor<std::decay_t<F>>(std::forward<F>(f));
}

Then use the helper function to construct a custom visitor from a lambda which captures this:

void ServiceClass::processResponse(std::string xml) {
    // Parse XML and do something only if certain elements present.

    tinyxml2::XMLDocument doc;
    doc.Parse(xml.c_str(), xml.length());

    auto visit = make_xml_visitor([this](const auto& elem) 
    { 
        this->changeState(elem.Name(), elem.GetText); 
    });
    doc.Accept(std::addressof(visit));
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
-2

The rule is that a function pointer must always accept a void * which is passed in to the module which calls it, and passed back. Or use a lambda which is the same thing with some of the machinery automated for you. (The void * is the "closure").

So

 typedef bool (*Callback)(string, string, void *context);


  class MyVisitor : public tinyxml2::XMLVisitor {
  public:

      bool VisitExit(const tinyxml2::XMLElement &e) {
          callback(e.Name(), e.GetText(), contextptr);
     }
     Callback callback;
     void *contextptr;
  }

  bool myCallBackFunc(string e, string v, void *context) {
     ServiceClass *service = (ServiceClass *) context; 
     cout << "Element " << e << " has value " << v << endl;
     service->ChangeState(e, v);
     return true;
  }
Malcolm McLean
  • 6,258
  • 1
  • 17
  • 18
  • Can the person who down-voted this reply please explain why? If there's a technical flaw, I'd like to understand it. Pointer-to-member functions are complicated, and challenging for many people. – CXJ Dec 17 '16 at 01:05
  • I didn't down vote this but the use of a void * to carry the function pointer is generally considered poor quality code and since there is a cast (a C brute forcecast at that) inside the function myCallBackFunc with the very type your question wanted to abstract away, it doesn't seem to do anything for you. – JimmyNJ Dec 17 '16 at 01:59
  • It's a flaw with C++. The modern way is to use a lambda. The void * is a way of setting up the lambda closure manually. void * is not the function pointer, but the context for the function pointer.However the callee is agnostic about the void * (in fact context is often null), abstraction is not your problem. It's making sure the types passed and received actually match.. – Malcolm McLean Dec 17 '16 at 10:34
  • My mistake. Yes void * is carrying the object instance - or context here - the method is on. Just as bad style however. And allowing void pointers in a call does nothing to help type matching. – JimmyNJ Dec 22 '16 at 23:59
  • In fact it's vital that a function pointer always take a void * for context, or you really do have bad style. The C++ compiler can't check for type safety, but that's not a reason to avoid a construct. – Malcolm McLean Dec 23 '16 at 11:15