membro atribuir com base no valor da cadeia
-
19-09-2019 - |
Pergunta
Eu preciso começar com o código, porque eu não sei o que a terminologia a utilizar. Vamos dizer que eu tenho o seguinte código:
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;
};
O que eu não suporto é a repetição de if (strcmp (attr-> name (), "x")!) Conversor >> X; Eu sinto que este é sujeito a erros e monótono, mas eu não consigo pensar em outra maneira de mapear um valor de string para uma atribuição de membro. Quais são algumas outras abordagens um pode tomar para evitar o código como este? A única outra alternativa possível que eu conseguia pensar era usar um hashmap, mas que é executado em problemas com retornos de chamada
Este é o melhor que pude com mas não é tão flexível quanto eu gostaria:
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;
};
Solução
Para a implementação com um mapa, você pode usar ponteiros-a-membros. Então você não vai precisar de uma cópia profunda do mapa (quando você copiá-lo, os ponteiros no mapa ainda apontam para o nó original), e que também irá permitir que você faça a coisa toda estático (este mapa é desnecessário por base -instance).
Por exemplo:
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;
}
Uso:
float Node::* member(varFromName(s));
if (member)
this->*member = xyz;
Este não é mais flexível, no entanto.
Para suportar diferentes tipos de membros, você pode modificar o acima para usar um mapa de corda para "variante de todos os tipos de membro suportados".
Por exemplo assim. O visitante membro setter deve ser reutilizável, e a única alteração no código, adicionar ou tipos de membro mudança, deve ser feito para o 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();
Este é, naturalmente, apenas um exemplo do que você pode fazer. Você pode escrever um static_visitor para fazer o que quiser. Por exemplo armazenagem de um fluxo e tentar extrair um valor do tipo certo para o membro dada.
Outras dicas
Use um array. Uma alternativa a esta union
seria deixar x
, y
e z
ser referências (float&
) para elementos da matriz 0, 1, 2 -. Ou (minha preferência) sempre chamá-los pelo número não pelo 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;
};