簡體   English   中英

如何解決C ++指針到成員函數的限制

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM