有没有一种从字符串表示中设置C / C ++成员变量的好方法? (内省-精简版)

时间:2009-11-06 14:16:47

标签: c++ boost macros introspection

我有一个结构,其中包含一些我希望能够从字符串中获取和设置的成员。鉴于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);

8 个答案:

答案 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)

有没有理由说词典/地图不起作用?您可以散列字符串以使查找更快。