如何解决 C++ 指向成员函数的指针限制

How to work around C++ pointer-to-member function limitation

C++ 使用指向成员函数的指针的能力有限。我需要一些允许我动态选择回调成员函数的东西,以便使用来自 TinyXML2 库的 XMLNode::Accept(XMLVisitor *visitor) 方法的访问者模式。

要使用 XMLNode::Accept(),我必须使用实现 XMLVisitor 接口的 class 来调用它。因此:

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

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

如果我的调用者不是一个想要使用它自己的方法之一作为回调函数的对象(以便它可以访问 class 变量),这就可以正常工作。例如,这有效:

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);
}

但是,在我的用例中,解析是在 class 中的一个方法内完成的。我有多个应用程序具有相似但独特的 classes。我只想使用一个通用的 MyVisitor class,而不是让访问者 class 对将调用它的每个 class 的内部结构有独特的了解。

因此,如果回调函数是每次调用 class 中的一个方法,这样我就可以影响从调用 class.[=21 实例化的对象的内部状态=]

顶级:我有 5 个服务器应用程序,它们与 5 个不同的贸易伙伴交谈,他们都发送 XML 响应,但每个都足够不同,每个服务器应用程序都有一个 class 这是该贸易伙伴所独有的。我正在努力遵循良好的 OO 和 DRY 设计,并避免额外的 classes 拥有独特的知识,同时仍然在做基本相同的工作。

这是我希望 Accept() 回调的 class 方法。

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

这是 class 方法,它将调用 Accept() 来遍历 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);
}

获得我想要的东西的简单方法是什么?我可以想象更多 classes 派生的 classes 对每种情况都是独一无二的,但这看起来非常冗长和笨拙。

注意:为了简洁起见,我上面的示例代码没有错误检查,没有空检查,甚至可能有小错误(例如,将 const char * 视为字符串 ;-)。

下面是您尝试在 C++11 中执行的操作的 std::bind(..) 示例。对于早期的 C++ 版本,您可以使用 boost::bind 实用程序。

顺便说一下,将您的 MyVisitor::VisitExit(...) 方法修正为 return 布尔值。

代码正在将 const char * 转换为 std::string。 tinyxml2 不保证来自 Name()GetText()char * 参数不为空。事实上,根据我的经验,它们在某些时候将为空。你应该提防这一点。为了不过多修改您的示例,我没有在示例中的任何地方都防止这种可能性。

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 ? "[=10=]" : e.Name(), e.GetText() == nullptr ? "[=10=]": 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);
}

所以在 ServiceClass 方法中你会做:

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);
}

规则是函数指针必须始终接受 void * 传递给调用它的模块,然后传回。或者使用 lambda,这与某些为您自动化的机器是一样的。 (void * 是 "closure")。

所以

 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;
  }

您可以使用泛型来支持您想要的任何回调。

我已经尝试模拟库的 类 以便为您提供一个完全可运行的示例:

#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;
}

首先定义一个模板和一个辅助函数:

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));
}

然后使用辅助函数从捕获 this:

的 lambda 构建自定义访问者
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));
}