2016-12-16 41 views
1

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

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

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

class MyVisitor : public tinyxml2::XMLVisitor { 
public: 
    bool VisitExit(const tinyxml2::XMLElement &e) { 
     callback(e.Name(), e.GetText()); 
    } 
    Callback 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); 
} 

但是,在我的用例中,解析是在一个类的方法内完成的。我有多个具有类似但独特的类的应用程序。我只想使用一个通用的类,而不是让访问者类对每个类的内部都有独特的知识,这些知识将称为它。

因此,如果回调函数是每个调用类中的一个方法,这样我就可以影响从该调用类实例化的对象的内部状态,这将很方便。

顶层:我有5个服务器应用程序与5个不同的贸易伙伴交谈,他们都发送XML响应,但每个服务器应用程序都有足够的不同,每个服务器应用程序都有一个对该贸易伙伴唯一的类。我试图遵循良好的面向对象和干型设计,并避免额外的类具有独特的知识,同时仍然在做基本相同的工作。

这里是我想要回拨的Accept()的类方法。

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

这里的类方法,它将调用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); 
} 

什么是简单的方式来获得我想要什么?我可以想象更多具有派生类的类,每种情况都是独一无二的,但是这看起来非常冗长和笨拙。注:为简洁起见,我上面的示例代码没有错误检查,没有空值检查,甚至可能有小错误(例如将const char *作为字符串处理;-)。

+1

你尝试过std :: bind吗? –

回答

4

下面是你想要在C++ 11中做什么的std :: bind(..)例子。对于早期的C++版本,您可以使用boost :: bind实用程序。

修复你的MyVisitor::VisitExit(...)方法返回一个布尔,顺便说一句。代码为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 ? "\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); 
} 
从服务类方法中

所以,你会怎么做:

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

不,我实际上并不是盲目地将const char *转换为std :: string。同样,我删除了所有错误和空检查。我试图让我的代码示例尽可能小,但仍然说明问题。我以前没有使用过std :: bind,并会给它看一看。 – CXJ

+0

这是一个错误? 'visitor_fn fn = std :: string(&ServiceClass:'?不应该是'visitor_fn fn = std :: bind(&ServiceClass:'instead? – CXJ

+0

是的,我会稍后再纠正的 – JimmyNJ

-2

的规则是一个函数指针必须接受这是在哪个调用它的模块通过一个void *,和传了回去。或者使用与您的一些自动化设备相同的lambda。 (void *是“闭包”)。

所以

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

对这个答复进行了投票的人请解释原因吗?如果存在技术缺陷,我想了解它。对于很多人来说是复杂的和具有挑战性的。 – CXJ

+0

我没有投票表决,但使用void *来携带函数指针通常被认为是劣质代码,并且因为里面有一个cast(一个C brute forcecast)函数myCallBackFunc与你的问题想抽象的类型非常类似,它似乎没有为你做任何事情 – JimmyNJ

+0

这是C++的一个缺陷,现代的方法是使用lambda void *是一种设置方法手动拉姆达关闭。 void *不是函数指针,而是函数指针的上下文。但是,被调用者不知道void *(事实上上下文通常是空的),抽象并不是你的问题。这是确保类型传递和接收实际匹配.. –

0

可以为了支持你想要哪个回调使用泛型。

我试图嘲弄库类,以给你一个完全可运行的例子:

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

然后使用帮助功能从一个lambda构建一个自定义访客,它捕获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)); 
}