[英]How to work around C++ pointer-to-member function limitation
C ++使用指針到成員函數的能力有限。 為了使用TinyXML2庫中XMLNode::Accept(XMLVisitor *visitor)
方法的Visitor模式,我需要能夠動態選擇回調成員函數的內容。
要使用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);
}
但是,在我的用例中,解析是在類的方法內部完成的。 我有多個具有相似但獨特的類的應用程序。 我只想使用一個通用的MyVisitor
類,而不是讓訪問者類對將要調用它的每個類的內部知識有獨特的了解。
因此,如果回調函數是每個調用類中的一個方法,將很方便,這樣我就可以影響從該調用類實例化的對象的內部狀態。
最高級別:我有5個服務器應用程序,它們與5個不同的貿易伙伴通信,它們都發送XML響應,但是每個服務器都有足夠的不同,以至於每個服務器應用程序都具有該貿易伙伴唯一的類。 我試圖遵循良好的OO和DRY設計,並避免額外的類具有獨特的知識,同時仍要進行基本相同的工作。
這是我希望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 *
視為字符串;-)。
下面是std :: bind(..)示例,您嘗試在C ++ 11中執行的操作。 對於早期的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方法中執行以下操作:
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);
}
您可以使用泛型來支持您想要的任何回調。
我試圖模擬庫的類,以便為您提供一個完全可運行的示例:
#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));
}
然后使用helper函數從捕獲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));
}
規則是,函數指針必須始終接受void *,該void *傳遞給調用它的模塊,然后傳遞回去。 或將lambda與為您自動化的某些機器相同。 (無效*是“關閉”)。
所以
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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.