无论如何将字符串转换为C ++ / Qt中结构中的字段?

时间:2016-09-17 13:44:47

标签: c++ qt

我有一个像这样的结构:

typedef struct {
    int field1;
    int field2;
} Example;

Example data;

我的真实结构有大约20个领域。但是,我想基于解析文本文件为这些字段赋值。现在,显而易见的方法是使用一个巨大的开关(或if-else if链)测试分配的属性。然而,扩展这是令人烦恼和复杂的。

是否有任何机制可以将从文件中提取的字符串转换为struct字段名?例如,如果我的文件有一行field1 = 23,我会知道在没有23的情况下将data.field1分配给switch

4 个答案:

答案 0 :(得分:2)

一种选择是使用Qt的元对象系统功能来按名称调用方法。

class SetFieldHelper : public QObject
{
    Q_OBJECT
public:
    explicit SetFieldHelper(Example &data) : m_data(data) {}

    void setField(const QString &line) {
        QStringList parts = line.split('=');
        if (parts.count() == 2) {
            const QByteArray field = parts[0].trimmed().toUtf8();
            const QString value = parts[1].trimmed();

            QMetaObject::invokeMethod(this, field.constData(), Q_ARG(QString, value));
        }
    }

private slots:
    void field1(const QString &value) {
        m_data.field1 = value.toInt();
    }

private:
    Example &m_data;
};

答案 1 :(得分:2)

使用名为X Macro的旧技术有一种巧妙的方法。

以下是一个例子:

struct A
{
    #define STRUCT_A_MEMBER_LIST \
        X(foo) \
        X(bar)

    #define X(name) int name;
    STRUCT_A_MEMBER_LIST
    #undef X

    void SetFieldByName(const char *field_name, int value)
    {
        #define X(name) if (!std::strcmp(field_name, #name)) name = std::atoi(value); else
        STRUCT_A_MEMBER_LIST return;
        #undef X
    }

    #undef STRUCT_A_MEMBER_LIST
}

可以使用哈希映射或其他方式替换if-else-if链,但无论哪种方式,您都不需要手动指定字段名称。

答案 2 :(得分:2)

您可以通过Q_GADGET利用Qt的元对象系统。这具有以下优点:

  1. 无需添加getter / setter - 如果可用,可以使用它们,但Qt可以直接访问原始或Qt类型的成员。

  2. 无需处理将字符串转换为目标类型。元属性系统会自动为您进行必要的转换。

  3. 元数据没有开销 - 它不会向目标类型添加任何数据,因为目标类型保持不变。

    即便如此,Q_PROPERTY是一个空宏,Q_GADGET会向该类添加一个静态方法。

  4. 如果你问我,这是一个非常好的杠杆:)

    // https://github.com/KubaO/stackoverflown/tree/master/questions/struct-deserialize-39547524
    #include <QtCore>
    #include <string>
    
    // Original API
    
    struct Example1 {
        int field1;
        double field2;
        bool field3;
        std::string field4;
    };
    
    // Type-Specific Adapter API
    
    template <typename T> struct GadgetTraits;
    
    struct Example1Gadget : Example1 {
        Q_GADGET
        Q_PROPERTY(int field1 MEMBER field1)
        Q_PROPERTY(double field2 MEMBER field2)
        Q_PROPERTY(bool field3 MEMBER field3)
        Q_PROPERTY(QString field4 READ getField4 WRITE setField4)
    public:
        void setField4(const QString & val) { field4 = val.toStdString(); }
        QString getField4() const { return QString::fromStdString(field4); }
    };
    template <> struct GadgetTraits<Example1> { using gadget_type = Example1Gadget; };
    
    // Generic Adapter API
    
    template <typename T>
    void set(T & gadget, const QStringList & data)
    {
        for (auto & entry : data) set(gadget, entry);
    }
    
    template <typename T>
    void set(T & gadget, const QString & data)
    {
        const QMetaObject & mo = GadgetTraits<T>::gadget_type::staticMetaObject;
        auto const fields = data.split('=');
        if (fields.length() != 2) return;
        auto field = fields[0].trimmed();
        auto value = fields[1].trimmed();
        auto i = mo.indexOfProperty(field.toUtf8());
        if (i < 0) return;
        auto prop = mo.property(i);
        prop.writeOnGadget(&gadget, value);
    }
    
    int main() {
        Example1 ex;
        set(ex, {"field1 = 3", "field2 = 1.5", "field3 = true", "field4 = foo bar"});
        Q_ASSERT(ex.field1 == 3);
        Q_ASSERT(ex.field2 == 1.5);
        Q_ASSERT(ex.field3 == true);
        Q_ASSERT(ex.field4 == "foo bar");
    }
    #include "main.moc"
    

答案 3 :(得分:0)

你需要写一个setter:

void setField1(const char *f) ;
void setField2(const char *f) ;

setter负责将字符串解码为int。

然后你需要一个关联数组(哈希),它将字段名称与函数相关联:

"field1" -> setField1
"field2" -> setField2

然后你需要从关联数组中获取函数指针并使用字段作为参数调用它。这是一个例子:

#include <iostream>

typedef struct {
  int field1;
  int field2;
} Example;

Example data;

void setField1(const char *f) {
  data.field1 = std::stoi(f); 
}

void setField2(const char *f) {
  data.field2 = std::stoi(f);
}

void (*tab[])(const char *) = { setField1, setField2 };

int main(void)
{
  data.field1 = 10 ;
  data.field2 = 20 ;
  tab[0]("43") ;
  tab[1]("12") ;
  std::cout << "fields:" << data.field1 << '|' << data.field2 << std::endl;
  return 0;
}

当然,您需要做的不仅仅是制作正确的代码。例如,Example应该是一个类,并且setter移动到那里,然后你需要解码文本行来获取索引,或者使用关联数组。