назначить член на основе строкового значения - PullRequest
2 голосов
/ 13 марта 2010

Мне нужно начать с кода, потому что я не уверен, какую терминологию использовать. Допустим, у меня есть следующий код:

 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;
 };

Что я не могу вынести, так это повторение конвертера if (! Strcmp (attr-> name (), "x")) >> x; Я чувствую, что это подвержено ошибкам и однообразно, но я не могу придумать другой способ сопоставить строковое значение с назначением члена. Какие еще подходы можно предпринять, чтобы избежать такого кода? Единственной возможной альтернативой, которую я мог придумать, было использование хэш-карты, но это сталкивается с проблемами с обратными вызовами

Это лучшее, что я мог бы сделать, но это не так гибко, как хотелось бы:

 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;
 };

Ответы [ 2 ]

4 голосов
/ 13 марта 2010

Для реализации с картой вы можете использовать указатели на члены. Тогда вам не понадобится глубокая копия карты (когда вы копируете ее, указатели на карте все еще указывают на исходный узел), и это также позволит вам сделать все это статичным (эта карта не нужна по инстанции).

Например:

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;
}

Использование:

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

Это не более гибко, хотя.

Для поддержки различных типов членов вы можете изменить вышеприведенное, чтобы использовать отображение строки для «варианта всех поддерживаемых типов элементов».

Например, так. Посетитель установщика элементов должен быть многоразовым, и единственное изменение в коде для добавления или изменения типов элементов должно быть сделано в 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();

Это, естественно, просто пример того, что вы можете сделать. Вы можете написать static_visitor, чтобы делать то, что вы хотите. Например, сохраняя поток и пытаясь извлечь значение правильного типа для данного члена.

0 голосов
/ 13 марта 2010

Использовать массив. Альтернативой этому union было бы разрешение x, y и z быть ссылками (float&) на элементы массива 0, 1, 2 - или (мое предпочтение) всегда вызывать их по номеру не по имени.

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;
 };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...