如何按名称公开类的字段

时间:2014-04-02 17:57:00

标签: c++ struct

我想定义结构来保存各种应用程序参数:

struct Params 
{
  String fooName;
  int    barCount;
  bool   widgetFlags;
  // ... many more
};

但我希望能够按名称枚举,获取和设置这些字段,例如,这样我就可以将它们暴露给自动化API并方便序列化:

Params p;
cout << p.getField("barCount");
p.setField("fooName", "Roger");
for (String fieldname : p.getFieldNames()) {
   cout << fieldname << "=" << p.getField(fieldName);
}

有没有一种很好的方法来定义从字符串标签到get / set函数的绑定?沿着这条线(非常多的伪代码):

Params() {
  addBinding("barCount", setter(&Params::barCount), getter(&Params::barCount));
  ...

我知道其他选项是从外部元数据文件自动生成结构,另一个选项是将结构存储为(键,值)对的表,但我宁愿将数据保存在结构中。

我确实有Variant类型,所有字段都可以转换为。

4 个答案:

答案 0 :(得分:1)

C ++没有反思,所以这不是你可以干净利落的事情。此外,通过将成员称为字符串,您必须尝试支持该语言的强类型性质。使用Boost Serializer或Google Protobuf等序列化库可能会更有用。

那就是说,如果我们允许一些可怕的事情,可以用XMacro做点什么。 (免责声明:我不建议实际这样做)。首先,您将所需的所有信息都放入宏

#define FIELD_PARAMS \
    FIELD_INFO(std::string, Name, "Name") \
    FIELD_INFO(int, Count, "Count")

或者作为头文件

<defs.h>
 FIELD_INFO(std::string, Name, "Name") \
 FIELD_INFO(int, Count, "Count")

然后,您将在类中定义FIELD_INFO,以表示成员声明或将其添加到地图

struct Params{
    Params() {
#define FIELD_INFO(TYPE,NAME,STRNAME) names_to_members.insert(std::make_pair(STRNAME,&NAME));
        FIELD_PARAMS
#undef FIELD_INFO
    }

    template <typename T>
    T& get(std::string field){
        return *(T*)names_to_members[field];
    }

    std::map<std::string, void*> names_to_members;

#define FIELD_INFO(TYPE,NAME,STRNAME) TYPE NAME;
    FIELD_PARAMS
#undef FIELD_INFO
};

然后你可以像这样使用它

int main (int argc, char** argv){
    Params myParams;
    myParams.get<std::string>("Name") = "Mike";
    myParams.get<int>("Count") = 38;

    std::cout << myParams.get<std::string>("Name"); // or myParams.Name
    std::cout << std::endl;
    std::cout << myParams.get<int>("Count"); // or myParams.Count

    return 0;
}

不幸的是,您仍然需要告诉编译器类型是什么。如果你有一个很好的变体类和适合它的库,你可以解决这个问题。

答案 1 :(得分:0)

我使用略有不同的存储空间:here。我使用的标签由于某种原因是int,但你也可以使用std :: string键。

答案 2 :(得分:0)

没有非常好的方法(无论如何“好”是一个非常主观的方面),因为你选择的任何技术都不是C ++语言本身的一部分,但如果你的目标是序列化,那么看一下{{3 }}

答案 3 :(得分:0)

我设法满足了我的特殊需求。 Ari的答案在将字符串映射到成员变量的引用方面最接近,尽管它依赖于void*的强制转换。我有一些类型更安全的东西:

有一个单独的PropertyAccessor的接口,它有一个从它派生的模板化类,它绑定到对特定成员变量的引用,并转换为Variant表示和从Variant表示转换:

class IPropertyAccessor
{
public:
    virtual ~IPropertyAccessor() {}
    virtual Variant     getValueAsVariant() const =0;
    virtual void        setValueAsVariant(const Variant& variant) =0;

};
typedef std::shared_ptr<IPropertyAccessor> IPropertyAccessorPtr;

template <class T>
class PropertyAccessor : public IPropertyAccessor
{
public:
    PropertyAccessor(T& valueRef_) : valueRef(valueRef_) {}
    virtual Variant getValueAsVariant() const {return VariantConverter<T>().toVariant(valueRef); }
    virtual void setValueAsVariant(const Variant& variant) {return VariantConverter<T>().toValue(variant); }

    T& valueRef;
};

    // Helper class to create a propertyaccessor templated on a type
template <class T>
    static IPropertyAccessorPtr createAccessor(T& valueRef_)
{
   return std::make_shared<PropertyAccessor<T>>(valueRef_);
}

公开集合的类现在可以定义ID - &gt; PropertyAccessor并通过引用绑定其值:

#define REGISTER_PROPERTY(field) accessorMap.insert(AccessorMap::value_type(#field, createAccessor(field)))
class TestPropertyCollection
{
public:
    typedef std::map<PropertyID, IPropertyAccessorPtr> AccessorMap;
    TestPropertyCollection()
    {
        REGISTER_PROPERTY(stringField1);
        // expands to 
        // accessorMap.insert(AccessorMap::value_type("stringField", createAccessor(stringField)));
        REGISTER_PROPERTY(stringField2);
        REGISTER_PROPERTY(intField1);
    }

  bool getPropertyVariant(const PropertyID& propertyID, Variant& retVal)
    {
        auto it = accessorMap.find(propertyID);
        if (it != accessorMap.end()) {
            auto& accessor = it->second;
            retVal = accessor->getValueAsVariant();
            return true;
        }
        return false;
    }

    String      stringField1;
    String      stringField2;
    int         intField1;

    AccessorMap accessorMap
};