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 *
视为字符串; - )。
答案 0 :(得分:4)
下面是您在C ++ 11中尝试做的事情的std :: bind(..)示例。对于早期的C ++版本,您可以使用boost :: bind实用程序。
顺便说一下,修复你的private string insertQuery = "INSERT INTO `virtualstartupdb`.`item` (`name`, `title`, `description`, `language_id`, `price`, `item_type_id`, `costs`) VALUES ( @name, @title, @description, @language_id, @price, @item_type_id, @costs );";
private string connectionString = "Connection String here";
public void InsertRecord(MyInsertType newData)
{
using (var cn = new MySqlConnection(connectionString))
using (var cmd = new MySqlCommand(insertQuery, cn))
{
//Guessing at column types and lengths here
cmd.Parameters.Add("@name", MySqlDbType.VarChar, 20).Value = newData._name;
cmd.Parameters.Add("@title", MySqlDbType.VarChar, 20).Value = newData._title;
cmd.Parameters.Add("@description", MySqlDbType.VarString, 1000).Value = newData._description;
cmd.Parameters.Add("@language_id", MySqlDbType.VarChar, 5).Value = newData._language_id;
cmd.Parameters.Add("@price", MySqlDbType.Decimal).Value = newData._price;
cmd.Parameters.Add("@item_type_id", MySqlDbType.Int64).Value = newData._item_type_id;
cmd.Parameters.Add("@costs", MySqlDbType.Decimal).Value = newData._costs;
cn.Open();
cmd.ExecuteNonQuery();
}
}
方法返回一个布尔值。
代码正在将MyVisitor::VisitExit(...)
转换为const char *
。 tinyxml2不保证来自std::string
或char *
的{{1}}参数不为空。事实上,根据我的经验,他们在某些时候将是空的。你应该防范这一点。为了不过多地修改你的例子,我没有在示例中到处保护这种可能性。
Name()
因此,您可以在ServiceClass方法中执行以下操作:
GetText()
答案 1 :(得分: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;
}
答案 2 :(得分: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));
}
答案 3 :(得分:-2)
规则是函数指针必须始终接受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;
}