我有一个结构,其中包含一些我希望能够从字符串中获取和设置的成员。鉴于C ++没有任何内省,我认为我需要一些宏的创造性解决方案,stringize运算符,也许boost::bind.
我不需要完全序列化或内省,更多的是'introspection-lite'
我想有一些与此相似的东西:
struct MyType {
int fieldA;
int fieldB;
};
DECLARE_STRING_MAP(MyType,fieldA);
DECLARE_STRING_MAP(MyType,fieldB);
MyType t;
SET_VALUE_FROM_STRING(MyType,t,"fieldA","3")
而不是拥有巨大的if
声明。
知道是否有一个简洁的解决方案吗?
相关问题:Object Reflection
编辑: 感谢maxim1000的'map to int Type :: *'技巧 - 这对我有用:
#define DEFINE_LOOKUP_MAP(Type) std::map<AnsiString,int Type::*> mapper
#define ADD_FIELD_MAPPING(Type, Field) mapper[#Field]=&Type::Field
#define SET_FIELD_FROM_MAP(Type, Field, var, value) var.*(mapper[#Field])=value
DEFINE_LOOKUP_MAP(MyType);
ADD_FIELD_MAPPING(MyType, fieldA);
ADD_FIELD_MAPPING(MyType, fieldB);
SET_FIELD_FROM_MAP(MyType, fieldA, obj, 3);
答案 0 :(得分:5)
如果所有类型都相同,您可以使用以下内容:
std::map<std::string,int MyType::*> mapper;
mapper["fieldA"]=&MyType::fieldA;
mapper["fieldB"]=&MyType::fieldB;
...
MyType obj;
obj.*(mapper["fieldA"])=3;
答案 1 :(得分:4)
死于宏。
我过去用过的一些无宏代码将名称绑定到struc成员并将非字符串类型转换为字符串:
#include <map>
#include <string>
#include <sstream>
template<class STRUC>
struct Field
{
virtual void set (STRUC& struc, const std::string& value) const = 0;
};
template<class STRUC, class FIELDTYPE>
struct FieldImpl : public Field<STRUC>
{
typedef FIELDTYPE (STRUC::*MemberPtr);
FieldImpl (MemberPtr memberPtr) {memberPtr_ = memberPtr;}
virtual void set (STRUC& struc, const std::string& value) const
{
std::istringstream iss (value);
iss >> struc.*memberPtr_;
}
private:
MemberPtr memberPtr_;
};
template<class STRUC>
class FieldMap
{
private:
typedef std::map<std::string, Field<STRUC>*> FieldNameMap;
FieldNameMap fieldMap_;
public:
~FieldMap ()
{
// delete fieldMap_ members.
}
void bind (const std::string& name, Field<STRUC>* field)
{
fieldMap_[name] = field;
}
template<typename FIELDTYPE>
void bind (const std::string& name, FIELDTYPE (STRUC::* member))
{
fieldMap_[name] = new FieldImpl<STRUC, FIELDTYPE> (member);
}
void setValue (STRUC& struc, const std::string& name, const std::string& value)
{
FieldNameMap::const_iterator iter = fieldMap_.find (name);
if (iter == fieldMap_.end ())
throw std::runtime_error (std::string ("No field binding found for ") + name);
(*iter).second->set (struc, value);
}
};
struct Test
{
int id;
double value;
std::string tag;
};
int main (int argc, char* argv[])
{
FieldMap<Test> fieldMap;
fieldMap.bind ("id", &Test::id);
fieldMap.bind ("value", &Test::value);
fieldMap.bind ("tag", &Test::tag);
Test test;
fieldMap.setValue (test, "id", "11");
fieldMap.setValue (test, "value", "1234.5678");
fieldMap.setValue (test, "tag", "hello");
return 0;
}
答案 2 :(得分:2)
我可以想到两个解决方案。
使用宏从同一来源创建结构定义及其地图
通过使用宏并重新编写结构定义,您可以使用此优秀答案中描述的技术,而无需单独声明地图。
像这样重写你的结构定义,并将它自己放在标题中:
BEGIN_STRUCT(MyType)
FIELD(int, fieldA);
FIELD(int, fieldB);
END_STRUCT
然后#include两次。在#including它之前第一次:
#define BEGIN_STRUCT(x) struct x {
#define FIELD(x, y) x y;
#define END_STRUCT };
第二次#include之前:
#define BEGIN_STRUCT(x) namespace x ## Mapping { typedef x MappedType;
#define FIELD mapper[#x]=&MappedType::x;
#define END_STRUCT }
我没有对此进行测试,因此可能会关闭一些细节。
如果您的环境中禁用了宏,您可以从您希望的任何外部工具(Perl,Python的Cog等)创建结构定义及其映射。
使用C ++的反射库
虽然C ++没有直接实现反射或内省,但可以使用附加库。我使用ROOT's Reflex库得到了很好的结果。
答案 3 :(得分:1)
如果你不愿意将结构更改为其他内容,那么你真的没有选择 - 你需要使用大的if语句来告诉你正在处理哪个字段。您可以使用宏隐藏它(并使其更容易编写),但它是相同的结构,您将不得不处理它。
这是一个如何编写宏的示例 - 它确实使用起来更简单,但它仍然不是“短”。
//Assumption: at the time you want to use this, you've got two strings, one with the
// name of the field to set (key), one with the value to set (value). I also assume
typedef struct MyType {
int fieldA;
int fieldB;
} MyType;
// fldnamedef - name of the field in the structure definition (const char *)
// convfunc - name of a function that takes a value, returns a fldtypedef
// s - structure to put data into
// key - const char * pointing to input field name
// value - const char * pointing to input field value
#define IF_FIELD_SET(fldnamedef, convfunc, s, key, value) {\
if (strcmp(#fldnamedef, key) == 0) {\
s.fldnamedef = convfunc(value);\
}\
}
int main()
{
MyType t={0,0};
IF_FIELD_SET(fieldA, atoi, t, "fieldA", "2");
printf("%d,%d\n",t.fieldA, t.fieldB);
}
这是IF_FIELD_SET线变成的预处理器输出:
{ if (strcmp("fieldA", "fieldA") == 0) { t.fieldA = atoi("2"); }};
答案 4 :(得分:1)
自省仿真?这听起来像是一个挑战,这是肯定的。
界面并没有让我满意,所以我建议另一种选择:
struct MyType
{
int fieldA;
int fieldB;
void setField(std::string const& field, std::string const& value);
};
现在挑战是setField
选择正确的字段,事实上地图似乎是合适的。但是我们需要在某处封装类型信息(除非你打算只使用整数,在这种情况下......没有任何困难),所以有一个仿函数的映射。
static std::map<std::string, Functor<MyType>*> M_Map;
// where Functor is
template <class Type>
struct Functor
{
virtual void set(Type& t, std::string const& value) const = 0;
};
// And a specialization would be
struct SetfieldA : public Functor<MyType>
{
virtual void set(MyType& t, std::string const& value) const
{
std::istringstream stream(value);
stream >> t.fieldA;
// some error handling could be welcome there :)
}
};
请注意std::istringstream
的使用,现在您可以支持任何类型,只要它们与std::istream
正确互动即可。因此,您可以支持用户定义的类。
当然,这里的部分都是关于自动化的!
像宏中的自动化。
#define INTROSPECTED(MyType_) \
private: \
typedef Functor<MyType_> intro_functor; \
typedef std::map<std::string, intro_functor const*> intro_map; \
static intro_map& IntroMap() { static intro_map M_; return M_; } \
public: \
static void IntroRegister(std::string const& field, intro_functor const* f){ \
IntroMap()[field] = f; } \
void setField(std::string const& field, std::string const& value) { \
intro_map::const_iterator it = IntroMap().find(field); \
if (it != IntroMap().end()) it->second->set(*this, value); }
#define INTROSPECT_FIELD(Class_, Name_) \
struct Set##Name_: public Functor<Class_> { \
virtual void set(Class_& t, std::string const& value) { \
std::istringstream stream(value); stream >> t.Name_; } } Setter##Name_; \
Class_::IntroRegister(#Name_, Setter##Name_)
这样的用法:
// myType.h
struct MyType
{
INTROSPECTED(MyType);
int fieldA;
int fieldB;
};
// myType.cpp
INTROSPECT_FIELD(MyType, fieldA);
INTROSPECT_FIELD(MyType, fieldB);
// Any file
MyType t;
t.set("fieldA", "3");
当然通常需要注意的是:从头顶开始,从不编译它,可能会杀死小猫,甚至更糟。
答案 5 :(得分:0)
这或多或少是“&lt;&lt;”运营商是为了。遗憾的是,该语言没有提供结构和类的默认版本,就像它对赋值运算符一样,但是你可以很容易地创建自己的版本。
答案 6 :(得分:0)
如果您愿意从结构转换到另一种数据类型,那么您有一些不同的选择。
如果字段都是相同的类型,只需使用STL地图:
typedef std :: map MyType;
MyType t;
t["fieldA"] = atoi("3");
printf("%d\n", t["fieldA"]);
如果它们属于不同的类型,那么当你将它们从结构中取出时,你可以继续转换它们:
typedef std::map<std::string, std::string> MyType;
MyType t;
t["fieldA"] = "3";
printf("%d\n", atoi(t["fieldA"]));
您可以将get和转换包装在特定于字段的宏中,以使其更容易编写。
typedef std::map<std::string, std::string> MyType;
#define fieldA(v) atoi(v["fieldA"])
MyType t;
t["fieldA"] = "3";
printf("%d\n", fieldA(v));
这样做的缺点是看起来不像结构元素访问。
您可以尝试将MyType设为类,并为每个字段使用单独的函数。这至少允许你为每个字段获得不同的类型,但你仍然需要有一大块ifs来做一组。当然,既然你可以把它放到对象中,它会更容易使用。当然,您已将结构字段访问转换为对象方法调用。不过,它很容易使用,这可能会给你带来一些东西。
class MyType {
public:
set(std::string key, std::string value) {
if (key == "fieldA") m_fieldA = atoi(value.c_str());
if (key == "fieldB") m_fieldB = atoi(value.c_str());
};
int fieldA() { return m_fieldA; };
int fieldB() { return m_fieldB; };
private:
int m_fieldA;
int m_fieldB;
};
MyType t;
t.set("fieldA", "3");
printf("%d\n", t.fieldA());
答案 7 :(得分:0)
有没有理由说词典/地图不起作用?您可以散列字符串以使查找更快。