开发者

Is there a good way of setting C/C++ member variables from string representations? (introspection-lite)

I've got a struct with some members that I want to be able to get and set from a string. Given that C++ doesn't have any introspection I figure I need some creative solution with macros, the stringize operator and maybe boost::bind. I don't need full serialization or introspection, more an 'introspection-lite'

I'd like to have something along the lines of this:

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

Rather than have a huge if statement.

Any idea if there's a neat solution to this?

Related question: Object Reflection

EDIT: Thanks to maxim1000 for the 'map to int Type::*' trick -- this worked for me:

#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_M开发者_JAVA技巧APPING(MyType, fieldA); 
ADD_FIELD_MAPPING(MyType, fieldB); 

SET_FIELD_FROM_MAP(MyType, fieldA, obj, 3);


If all of them have the same type, you can use something like this:

std::map<std::string,int MyType::*> mapper;
mapper["fieldA"]=&MyType::fieldA;
mapper["fieldB"]=&MyType::fieldB;
...
MyType obj;
obj.*(mapper["fieldA"])=3;


Death to macros.

Some macro-free code i've used in the past for binding names to struc members and converting non-string types to string:

#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;
}


I can think of two solutions.

Use macros to create a structure definition and its map from the same source

By using macros and reworking your structure definition, you can use the technique as described in this excellent answer without separately declaring the map.

Rewrite your structure definition like this, and put it in a header by itself:

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

Then #include it twice. Before #including it the first time:

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

Before #including it the second time:

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

I've not tested this, so a few details may be off.

If macros are banned in your environment, you could instead create the structure definition and its map from whatever external tool you wish (Perl, Python's Cog, etc.).

Use a reflection library for C++

Although C++ doesn't directly implement reflection or introspection, add-on libraries are available. I've used ROOT's Reflex library with good results.


If you aren't willing to change the struct to something else, you really don't have a choice - you're going to need the big if statement to tell which field you're dealing with. You can hide it (and make it easier to write) with macros, but it's the same structure, and you're going to have to just deal with it.

Here's an example of how you might write that macro - it does make usage simpler, but it's still not 'short' by any means.

//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);
}

And here's the pre-processor output that the IF_FIELD_SET line turns into:

{ if (strcmp("fieldA", "fieldA") == 0) { t.fieldA = atoi("2"); }};


Introspection emulation ? That sounds like a challenge, that's for sure.

The interface does not really please me, so I would propose an alternative:

struct MyType
{
  int fieldA;
  int fieldB;

  void setField(std::string const& field, std::string const& value);
};

Now the challenge is for setField to select the right field, and indeed a map seems appropriate. However we need to encapsulate the type information somewhere (unless you plan on using only ints, in which case... there is no difficulty), so a map of functors is in order.

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 :)
  }
};

Note the use of std::istringstream, now you can support any type as long as they correctly interact with an std::istream. Thus you can support user defined classes.

And of course, the part here is all about automation!

And automation like in macros.

#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_)

Usage like this:

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

Of course the usual caveat apply: off the top of my head, never compiled it, may kill kittens and worse.


This is more or less what the "<<" operator is for. Sadly, the language doesn't provide a default version for structs and classes like it does for the assignment operator, but you can make your own easily enough.


If you are willing to change away from a structure to another data type, then you have some different options.

If the fields are all of the same type, just use an STL map:

typedef std::map MyType;

MyType t;

t["fieldA"] = atoi("3");
printf("%d\n", t["fieldA"]);

If they are of different types, then you could go with converting the values when you take them out of the structure:

typedef std::map<std::string, std::string> MyType;

MyType t;
t["fieldA"] = "3";

printf("%d\n", atoi(t["fieldA"]));

You can wrap the get and the conversion in field-specific macros, to make it a little easier to write.

typedef std::map<std::string, std::string> MyType;
#define fieldA(v) atoi(v["fieldA"])

MyType t;
t["fieldA"] = "3";

printf("%d\n", fieldA(v));

That does have the disadvantage of not looking much like a structure element access.

You might try making MyType a class and use individual functions for each field. This at least allows you to get different types per field, but you'll still have to have the big block of ifs to do a set. Of course, since you can put that into the object, it'll be easier to use. Of course, you've turned structure field access into object method calls. Still, it's pretty easy to use, and that might buy you something.

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


Is there any reason that a dictionary/map wouldn't work? You could hash the strings to make lookup faster.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜