我有一些代码用于将任意QObject子类转换为JSON。如果它们是指向子类的指针,我可以转换它们,但我很好奇是否可以转换实例(前提是子类实现了复制构造函数)。有没有一些疯狂的方法可以使用模板或QMetaType
提供的类型信息来复制QObject
子类的实例而不知道它是什么?ToJson
代码位于不了解子类的类中。
我认为使用QMetaType::create
或类似的东西可能是可能的,但我无法弄清楚如何实际复制子类实例的属性。
这是我的转换代码:
QJsonValue ToJson(QVariant value){
switch(value.type()){
case QVariant::Int:
case QVariant::Double:
return value.toDouble();
////Other cases, etc...
case QVariant::UserType:
QObject* obj_ptr = qvariant_cast<QObject*>(value);
if(obj_ptr) // value was originally a pointer to a QObject, works correctly
return ToJson(obj_ptr);
else { // value was orginally an instance of a QObject subclass
std::string t = value.typeName(); //returns "MyQObject"
int id = QMetaType::type(t.c_str()); //returns the id of the derived class
void* v = QMetaType::create(id, &value); //passing &value does nothing
obj_ptr = static_cast<QObject*>(v);
return ToJson(obj_ptr); //works, but resulting fields are all default
}
}
}
QJsonObject ToJson(QObject* o){
QJsonObject obj;
auto mo = o->metaObject();
for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i){
QVariant value = o->property(mo->property(i).name());
obj[mo->property(i).name()] = ToJson(value);
}
return obj;
}
示例代码用例:
qRegisterMetaType<MyQObject>();
MyQObject obj;
obj.db = 11.1;
QVariant test1 = QVariant::fromValue(obj);
QVariant test2 = QVariant::fromValue(&obj);
QJsonValue v1 = ToJson(test1); // default constructed values
QJsonValue v2 = ToJson(test2); // db = 11.1
示例 QObject 子类:
class MyQObject : public QObject {
Q_OBJECT
Q_PROPERTY(double DB MEMBER db)
Q_PROPERTY(int I MEMBER i)
public:
MyQObject();
MyQObject(const MyQObject& other) : QObject() {
i = other.i;
db = other.db;
}
int i = 50;
double db = 1.5;
};
Q_DECLARE_METATYPE(MyQObject)
有没有办法处理上面test1
说明的情况?
长话短说:没有。无法将QObject
按值存储在容器或QVariant
中。
Qt禁止复制QObjects和所有继承类。强制的Q_OBJECT
宏将禁用新定义的类中的任何复制构造函数。
在MyObject
类中定义的复制构造函数缺少基类构造函数调用。如果QObject
有一个复制构造函数,它将是这样的:
MyQObject(const MyQObject& other) :
QObject(other) // this will NEVER compile
{
i = other.i;
db = other.db;
}
编译器可能会给你一个警告,但允许你拥有这样的构造函数,即使它会导致未定义的行为,或者每次按值传递MyObject
实例时都会切片。
此外,Qt文档指出以下内容:
存储在各种容器中的值可以是任何可分配的 数据类型。若要符合条件,类型必须提供默认构造函数,即 复制构造函数和赋值运算符。这涵盖了大多数数据 您可能希望存储在容器中的类型,包括基本 类型(如 int 和 double)、指针类型和 Qt 数据类型(如 QString,QDate和QTime,但它不包括QObject或任何QObject 子类(QWidget,QDialog,QTimer等)。
因此,除非将QObject
类和派生类存储为指针,否则您无法将它们存储为 Qt 容器,因为设计禁用了 QObjects 的副本。
此外,如果你想利用多态行为,你必须使用指针,即使在你的代码中没有明确需要强制转换为派生类,据我所知。如果你真的需要在某个地方求助于投射,你可以考虑让你的ToJson成为一个模板函数。
有一个解决方案,但要谨慎,因为它仅在以下情况下才合理/适用:
- 有问题的类主要是数据存储类
- 有问题的类如果不继承自
QObject
,则完全可以复制 - 最重要的是,你让类从
QObject
继承的唯一原因是它可以具有元属性。
如果你的代码出于获取元信息以外的任何原因将该类用作QObject
,那么如果您尝试按值存储它,您几乎可以肯定是错误地使用它(正如 G. Giordano 在他们的答案中所解释的那样)。
撇开误用注意事项不谈,为了对按值存储QObject
子类的QVariant
进行 JSON 化,您可以使用QMetaType::create
方法并向其传递用户类型 id 和yourQVariant.constData()
。
例:
MyQObject obj;
obj.db = 11.1;
QVariant value = QVariant::fromValue(obj);
std::string t = value.typeName();
int id = QMetaType::type(t.c_str());
void* v = QMetaType::create(id, value.constData());
obj_ptr = static_cast<QObject*>(v);
QJsonValue json = ToJson(obj_ptr); //json contains db = 11.1