Domanda

Ho bisogno iniziare con il codice perché non sono sicuro di quello che la terminologia da usare. Diciamo che ho il seguente codice:

 class Node
 { 
 public:
  void Parse(rapidxml::xml_node<> *node) 
  {
   for (rapidxml::xml_attribute<> *attr = node->first_attribute();
        attr;
        attr = attr->next_attribute())
   {
    std::stringstream converter;
    converter << attr->value();

    if( !strcmp(attr->name(), "x") ) converter >> x;
    else if( !strcmp(attr->name(),"y") ) converter >> y;
    else if( !strcmp(attr->name(), "z") ) converter >> z;
   }
  }

 private:
  float x;
  float y;
  float z;
 };

Quello che non sopporto è la ripetizione di se (strcmp (attr-> nome (), "x")!) Convertitore >> x; Sento che questo è soggetto ad errori e monotono, ma non riesco a pensare ad un altro modo per mappare un valore di stringa per un incarico di membro. Quali sono alcuni altri approcci si possono adottare per evitare il codice di questo tipo? L'unica altra alternativa possibile che riuscivo a pensare era quello di utilizzare un HashMap, ma che corre in problemi con callback

Questo è il meglio che potevo con ma non è flessibile come vorrei:

 class Node
 {
  Node() : x(0.0f), y(0.0f), z(0.0f) 
  {
   assignmentMap["x"] = &x;
   assignmentMap["y"] = &y;
   assignmentMap["z"] = &z;
  }

 public:
  void Parse(rapidxml::xml_node<> *node) 
  {
   for (rapidxml::xml_attribute<> *attr = node->first_attribute();
        attr;
        attr = attr->next_attribute())
   {
    map<std::string, float*>::iterator member = assignmentMap.find(attr->name());
    //check for a pre-existing entry
    if( member == assignmentMap.end()) continue;

    std::stringstream converter;
    converter << attr->value();
    converter >> *(member->second);
   }
  }

 private:
  float x;
  float y;
  float z;

  std::map<std::string, float*> assignmentMap;
 };
È stato utile?

Soluzione

Per l'attuazione di una mappa, è possibile utilizzare i puntatori-a-membri. Allora non avrete bisogno di una copia completa della mappa (quando si copia, i puntatori nella mappa indicano ancora nel nodo originale), e sarà anche consentire di fare il tutto statico (questa mappa non è necessaria in ogni -instance base).

Ad esempio:

class Node {
    //...
    static std::map<std::string, float Node::*> initVarMap();
    static float Node::* varFromName(const std::string& name);
};

std::map<std::string, float Node::*> Node::initVarMap()
{
    std::map<std::string, float Node::*> varMap;
    varMap["x"] = &Node::x;
    varMap["y"] = &Node::y;
    varMap["z"] = &Node::z;
    return varMap;
}

float Node::* Node::varFromName(const std::string& name)
{
    static std::map<std::string, float Node::*> varMap = initVarMap();
    std::map<std::string, float Node::*>::const_iterator it = varMap.find(name);
    return it != varMap.end() ? it->second : NULL;
}

Utilizzo:

    float Node::* member(varFromName(s));
    if (member)
        this->*member = xyz;

Questa non è più flessibile, però.

Per supportare diversi tipi di membri, si potrebbe modificare la sopra per usare una mappa di stringa "variante di tutti i tipi di membro supportati".

Ad esempio così. Il visitatore membro setter dovrebbe essere riutilizzabile, e l'unica modifica al codice, per aggiungere o modificare i tipi di membri, dovrebbe essere fatto per il typedef.

 #include <map>
 #include <string>
 #include <iostream>
 #include <boost/variant.hpp>

template <class Obj, class T>
struct MemberSetter: boost::static_visitor<void>
{
    Obj* obj;
    const T* value;
public:
    MemberSetter(Obj* obj, const T* value): obj(obj), value(value) {}

    void operator()(T Obj::*member) const
    {
        obj->*member = *value;
    }
    template <class U>
    void operator()(U Obj::*) const
    {
        //type mismatch: handle error (or attempt conversion?)
    }
};

class Node
{
public:
    Node() : i(0), f(0.0f), d(0.0f)
    {
    }

    template <class T>
    void set(const std::string& s, T value)
    {
        std::map<std::string, MemberTypes>::const_iterator it = varMap.find(s);
        if (it != varMap.end()) {
            boost::apply_visitor(MemberSetter<Node, T>(this, &value), it->second);
        } //else handle error
    }
    void report() const
    {
        std::cout << i << ' ' << f << ' ' << d << '\n';
    }
private:
    int i;
    float f;
    double d;

    typedef boost::variant<int Node::*, float Node::*, double Node::*> MemberTypes;
    static std::map<std::string, MemberTypes> initVarMap();
    static std::map<std::string, MemberTypes> varMap;
};

int main()
{
    Node a;
    a.set("i", 3);
    a.set("d", 4.5);
    a.set("f", 1.5f);
    a.report();
}

std::map<std::string, Node::MemberTypes> Node::initVarMap()
{
    std::map<std::string, Node::MemberTypes> varMap;
    varMap["i"] = &Node::i;
    varMap["f"] = &Node::f;
    varMap["d"] = &Node::d;
    return varMap;
}

std::map<std::string, Node::MemberTypes> Node::varMap = Node::initVarMap();

Questo è naturalmente solo un esempio di cosa si può fare. È possibile scrivere una static_visitor a fare quello che vuoi. memorizzazione per esempio un flusso e il tentativo di estrarre un valore del tipo giusto per il membro data.

Altri suggerimenti

Utilizzare un array. Un'alternativa a questo union sarebbe quello di lasciare che x, y, e z essere riferimenti (float&) a elementi di matrice 0, 1, 2 -. O (la mia preferenza) sempre chiamarli per numero e non per nome

class Node
 { 
 public:
  void Parse(rapidxml::xml_node<> *node) 
  {
   std::stringstream converter;

   for (rapidxml::xml_attribute<> *attr = node->first_attribute();
        attr;
        attr = attr->next_attribute())
   {
    if ( strlen( attr->name() ) != 1
     || *attr->name() < 'x' || *attr->name() > 'z' )
        throw rapidxml::parse_error; // or whatever

    converter << attr->value() >> ary[ *attr->name() - 'x' ];
   }
  }

 private:
  union {
    float ary[3]; // this can come in handy elsewhere
    struct {
      float x;
      float y;
      float z;
    } dim;
 };
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top