我有一个像这样的结构:
typedef struct {
int field1;
int field2;
} Example;
Example data;
我的真实结构有大约20个领域。但是,我想基于解析文本文件为这些字段赋值。现在,显而易见的方法是使用一个巨大的开关(或if-else if
链)测试分配的属性。然而,扩展这是令人烦恼和复杂的。
是否有任何机制可以将从文件中提取的字符串转换为struct字段名?例如,如果我的文件有一行field1 = 23
,我会知道在没有23
的情况下将data.field1
分配给switch
。
答案 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的元对象系统。这具有以下优点:
无需添加getter / setter - 如果可用,可以使用它们,但Qt可以直接访问原始或Qt类型的成员。
无需处理将字符串转换为目标类型。元属性系统会自动为您进行必要的转换。
元数据没有开销 - 它不会向目标类型添加任何数据,因为目标类型保持不变。
即便如此,Q_PROPERTY
是一个空宏,Q_GADGET
会向该类添加一个静态方法。
如果你问我,这是一个非常好的杠杆:)
// 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移动到那里,然后你需要解码文本行来获取索引,或者使用关联数组。