C++ 如何绕过C++;指向成员函数限制的指针
C++使用指向成员函数的指针的能力有限。为了使用TinyXML2库中C++ 如何绕过C++;指向成员函数限制的指针,c++,tinyxml2,C++,Tinyxml2,C++使用指向成员函数的指针的能力有限。为了使用TinyXML2库中XMLNode::Accept(XMLVisitor*Visitor)方法的访问者模式,我需要能够动态选择回调成员函数的东西 要使用XMLNode::Accept(),我必须使用实现XMLVisitor接口的类来调用它。因此: typedef bool (*Callback)(string, string); class MyVisitor : public tinyxml2::XMLVisitor { public:
XMLNode::Accept(XMLVisitor*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);
}
下面是调用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++版本,可以使用Booo::绑定实用程序。
顺便说一下,修复您的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);
}
规则是函数指针必须始终接受一个void*,它被传递到调用它的模块中,并被传递回。或者使用lambda,这与一些为您自动化的机器是一样的。(空白*为“关闭”) 所以
typedef bool(*回调)(字符串,字符串,void*上下文);
类MyVisitor:public tinyxml2::XMLVisitor{
公众:
bool VisitExit(const tinyxml2::xmlement&e){
回调(e.Name(),e.GetText(),contextptr);
}
回调;
void*contextptr;
}
bool myCallBackFunc(字符串e、字符串v、void*上下文){
ServiceClass*服务=(ServiceClass*)上下文;
cout您可以使用泛型来支持您想要的任何回调
为了给您提供一个完全可运行的示例,我尝试模拟库的类:
#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;
}
#包括
#包括
#包括
类XmlNode{
公众:
XmlNode(const std::string&n,const std::string t):名称(n),文本(t){}
常量std::string&Name()常量{return Name;}
常量std::string&GetText()常量{return txt;}
私人:
std::字符串名;
std::string-txt;
};
类XMLVisitor{
公众:
虚拟void VisitExit(const XmlNode&node)=0;
虚拟~XMLVisitor(){}
};
模板
类MyVisitor:XMLVisitor{
公众:
MyVisitor(){}
void myInnerPrint(常量XmlNode&node){
std::cout首先定义一个模板和一个助手函数:
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));
}
你试过std::bind吗?不,我实际上并没有盲目地将const char*转换为std::string。同样,我删除了所有错误和空检查。我试图使我的代码示例尽可能小,但仍然说明了这个问题。我以前没有使用过std::bind,我会看一看。这是打字错误吗?visitor\u fn fn=std::string(&ServiceClass:
?不应该是visitor\u fn fn=std::bind吗(&ServiceClass:
相反?在我的小测试和初始服务器进程测试中,此解决方案似乎工作正常。非常感谢!否决此答复的人能否解释原因?如果存在技术缺陷,我想了解它。指向成员函数的指针很复杂,对许多人来说都很有挑战性。我dn不反对这一点,但使用void*来携带函数指针通常被认为是低质量代码,因为存在强制转换(C brute forcecast)在函数MyCaleButoFunc中,你的问题非常抽象,它似乎对你没有任何作用。它是C++的一个缺陷。现代方法是使用lambda。空洞*是手动设置lambda闭包的一种方式。Value*不是函数指针,而是函数指针的上下文。对void*不可知(事实上上下文通常为空),抽象不是你的问题。它是确保传递和接收的类型实际匹配。我的错误。是的,void*携带了对象实例-或上下文-方法是打开的。但是,就像糟糕的样式一样。允许调用中使用void指针无助于类型匹配。事实上,函数指针始终使用vo是至关重要的对于上下文,或者确实是有坏的风格。C++编译器不能检查类型安全性,但这不是避免构造的原因。
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));
}
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));
}