назначить член на основе строкового значения

StackOverflow https://stackoverflow.com/questions/2437270

  •  19-09-2019
  •  | 
  •  

Вопрос

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

 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") ) Converter >> 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;
 };
Это было полезно?

Решение

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

Например:

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, чтобы делать то, что хотите.Например, сохранение потока и попытка извлечь значение правильного типа для данного члена.

Другие советы

Используйте массив.Альтернатива этому 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;
 };
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top