代码之家  ›  专栏  ›  技术社区  ›  the_mandrill

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

  •  5
  • the_mandrill  · 技术社区  · 16 年前

    我有一个包含一些成员的结构,我希望能够从字符串中获取和设置这些成员。假设C++没有任何内省,我想我需要一些宏的解决方案,StrugIDS操作符,也许 boost::bind. 我不需要完整的序列化或内省,更需要一个“内省精简版”

    我想做一些类似的事情:

    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的“映射到int类型::*”技巧——这对我很有用:

    #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 回复  |  直到 9 年前
        1
  •  6
  •   maxim1000    16 年前

    如果它们都具有相同的类型,则可以使用如下内容:

    std::map<std::string,int MyType::*> mapper;
    mapper["fieldA"]=&MyType::fieldA;
    mapper["fieldB"]=&MyType::fieldB;
    ...
    MyType obj;
    obj.*(mapper["fieldA"])=3;
    
        2
  •  4
  •   jon hanson    14 年前

    宏的死亡。

    #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;
    }
    
        3
  •  2
  •   Josh Kelley    16 年前

    我可以想出两种解决办法。

    通过使用宏和修改结构定义,您可以使用此优秀答案中描述的技术,而无需单独声明映射。

    BEGIN_STRUCT(MyType)
    FIELD(int, fieldA);
    FIELD(int, fieldB);
    END_STRUCT
    

    然后#包括两次。在第一次包括之前:

    #define BEGIN_STRUCT(x) struct x {
    #define FIELD(x, y) x y;
    #define END_STRUCT };
    

    在#包括第二次之前:

    #define BEGIN_STRUCT(x) namespace x ## Mapping { typedef x MappedType;
    #define FIELD mapper[#x]=&MappedType::x;
    #define END_STRUCT }
    

    如果在您的环境中禁止使用宏,您可以使用任何您想要的外部工具(Perl、Python的 Cog

    虽然C++没有直接实现反射或内省,但附加库是可用的。我用过 ROOT's Reflex

        4
  •  1
  •   Michael Kohne    16 年前

    如果您不愿意将结构更改为其他内容,那么您真的没有选择的余地——您将需要一个大的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"); }};
    
        5
  •  1
  •   Orwellophile    8 年前

    该界面并不真正让我满意,因此我会提出一个替代方案:

    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");
    

        6
  •  0
  •   T.E.D.    16 年前

        7
  •  0
  •   Michael Kohne    16 年前

    如果您愿意从一种结构更改为另一种数据类型,那么您有一些不同的选择。

    如果所有字段都是同一类型,只需使用STL映射即可:

    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());
    
        8
  •  0
  •   jessecurry    16 年前

    有没有任何理由说明字典/地图不起作用?您可以散列字符串以加快查找速度。