5

我有一个结构,其中包含一些我希望能够从字符串中获取和设置的成员。鉴于 C++ 没有任何内省,我认为我需要一些具有宏的创造性解决方案,字符串化运算符,也许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声明。

知道是否有一个巧妙的解决方案吗?

相关问题:对象反射

编辑:感谢 maxim1000 的“映射到 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);
4

8 回答 8

6

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

std::map<std::string,int MyType::*> mapper;
mapper["fieldA"]=&MyType::fieldA;
mapper["fieldB"]=&MyType::fieldB;
...
MyType obj;
obj.*(mapper["fieldA"])=3;
于 2009-11-06T14:35:36.163 回答
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;
}
于 2009-11-06T17:14:49.040 回答
2

我可以想到两种解决方案。

使用宏从同一来源创建结构定义及其映射

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

像这样重写您的结构定义,并将其单独放在标题中:

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

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

#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 的 Reflex库,效果很好。

于 2009-11-06T15:23:29.967 回答
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"); }};
于 2009-11-06T14:41:43.873 回答
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");

当然,通常的警告适用:在我的脑海中,从未编译过它,可能会杀死小猫甚至更糟。

于 2009-11-06T15:13:45.710 回答
0

这或多或少是“<<”运算符的用途。遗憾的是,该语言没有像赋值运算符那样为结构和类提供默认版本,但您可以轻松地制作自己的版本。

于 2009-11-06T14:44:47.563 回答
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"]));

您可以将获取和转换包装在特定于字段的宏中,以使其更容易编写。

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());
于 2009-11-06T15:00:56.533 回答
0

字典/地图不起作用有什么原因吗?您可以散列字符串以加快查找速度。

于 2009-11-06T15:12:30.597 回答