назначить член на основе строкового значения
-
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;
};