无法从文件中读取 QList<Class*>
Posted
技术标签:
【中文标题】无法从文件中读取 QList<Class*>【英文标题】:Can't read QList<Class*> from a file 【发布时间】:2014-06-12 13:29:02 【问题描述】:我对流运算符有疑问>>。我正在尝试将自定义对象的 QList 保存并加载到文件中。保存例程似乎工作正常,但读取文件会导致崩溃。我准备了一个非常简单的例子。首先是自定义类:
class CustomObject : public QObject
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
public:
explicit CustomObject(QObject *parent = 0);
CustomObject(const CustomObject & copy, QObject *parent = 0);
QString name() const;
void setName( const QString & name);
private:
QString m_name;
;
Q_DECLARE_METATYPE( CustomObject )
QDataStream& operator<<( QDataStream& dataStream, const CustomObject * item );
QDataStream& operator>>( QDataStream& dataStream, CustomObject * item );
我已经以这种方式实现了流操作符:
QDataStream &operator<<(QDataStream &dataStream, const CustomObject *item)
for(int i = 0; i < item->metaObject()->propertyCount(); ++i)
if(item->metaObject()->property(i).isStored(item))
dataStream << item->metaObject()->property(i).read(item);
return dataStream;
QDataStream &operator>>(QDataStream &dataStream, CustomObject *item)
QVariant var;
for(int i = 0; i < item->metaObject()->propertyCount(); ++i)
if(item->metaObject()->property(i).isStored(item))
dataStream >> var;
item->metaObject()->property(i).write(item, var);
return dataStream;
这是save()
函数(m_objectsList
是QList<CustomObject*>
)
QFile saveFile("/tmp/SaveFile");
if(!saveFile.open(QIODevice::WriteOnly))
qWarning() << saveFile.errorString();
return;
QDataStream outStream(&saveFile);
outStream.setVersion(QDataStream::Qt_4_8);
outStream << m_objectsList;
saveFile.close();
这是read()
例程:
QFile saveFile("/tmp/SaveFile");
if(!saveFile.open(QIODevice::ReadOnly))
qWarning() << saveFile.errorString();
return;
QDataStream inStream(&saveFile);
inStream >> m_objectsList;
saveFile.close();
应用程序在for循环的条件语句处出现segfault in operator>>:
i < item->metaObject()->propertyCount()
item
不可访问。
你能解释一下错误在哪里吗?
非常感谢。
【问题讨论】:
在 Qt 5 中,保存文件时应使用QSaveFile
而不是QFile
。
感谢您的提示,我正在慢慢转向 qt5
【参考方案1】:
您的代码崩溃了,因为在您的输入运算符中,item
指针实际上并未指向对象,并且可能为空。为了解决这个问题,操作员应该引用指针,并创建一个新的 CustomObject() 实例。像这样的:
QDataStream &operator>>(QDataStream &dataStream, CustomObject *& item)
QVariant var;
item = new CustomObject();
for(int i = 0; i < item->metaObject()->propertyCount(); ++i)
if(item->metaObject()->property(i).isStored(item))
dataStream >> var;
item->metaObject()->property(i).write(item, var);
return dataStream;
【讨论】:
现在使用你的行,应用程序可以成功传递运算符>>,但是我在尝试从存储在 QList 中的对象中获取属性时遇到了段错误:foreach (CustomObject *obj, m_objectsList) qDebug() 名称();调试器指向 qstring.h 的第 725 行: inline QString::QString(const QString &other) : d(other.d) Q_ASSERT(&other != this); d->ref.ref(); (name()
应该返回一个QString
)【参考方案2】:
您的代码出现段错误,因为item
是一个悬空指针。没有人初始化它。在阅读之前实例化 item
是您的责任。
您可能还应该尝试存储动态属性,并确保至少有一些向后兼容的潜力。
下面的代码提供了两个序列化对象列表(由其属性定义)的模板函数。首先,让我们重新迭代一下对象类型:
// https://github.com/KubaO/***n/tree/master/questions/prop-storage-24185694
#include <QtCore>
class CustomObject : public QObject
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName STORED true)
QString m_name;
public:
#ifdef Q_MOC_RUN
Q_INVOKABLE CustomObject(QObject *parent = )
#endif
using QObject::QObject;
QString name() const return m_name;
void setName(const QString &name) m_name = name;
;
一些帮手:
/// Returns a zero-copy byte array wrapping a C string constant
static QByteArray baFromCStr(const char *str)
return QByteArray::fromRawData(str, qstrlen(str));
/// Returns a list of stored properties for a given type
QList<QMetaProperty> storedProperties(const QMetaObject *mo)
QList<QMetaProperty> stored;
for (int i = 0; i < mo->propertyCount(); ++i)
auto prop = mo->property(i);
if (prop.isStored())
stored << prop;
return stored;
/// Caches strings for saving to a data stream
struct SaveCache
QMap<QByteArray, qint32> strings;
QDataStream &save(QDataStream &str, const QByteArray &string)
auto it = strings.find(string);
if (it != strings.end())
return str << (qint32)it.value();
auto key = strings.count();
strings.insert(string, key);
return str << (qint32)key << string;
QDataStream &save(QDataStream &str, const char *string)
return save(str, baFromCStr(string));
;
/// Caches strings while loading from a data stream
struct LoadCache
QList<QByteArray> strings;
QDataStream &load(QDataStream &str, QByteArray &string)
qint32 index;
str >> index;
if (index >= strings.count())
str >> string;
while (strings.size() < index)
strings << QByteArray;
strings << string;
else
string = strings.at(index);
return str;
;
流中存储的格式可以描述如下:
template <typename T>
QDataStream &writeObjectList(QDataStream &str, const QList<T*> &items)
str << (quint32)items.count();
if (! items.count()) return str;
str << (quint8)1; // version
SaveCache strings;
for (QObject *item : items)
auto *mo = item->metaObject();
// Type
strings.save(str, mo->className());
// Properties
auto const stored = storedProperties(mo);
auto const dynamic = item->dynamicPropertyNames();
str << (quint32)(stored.count() + dynamic.count());
for (auto &prop : qAsConst(stored))
strings.save(str, prop.name()) << prop.read(item);
for (auto &name : dynamic)
strings.save(str, name) << item->property(name);
return str;
读取方法要尝试两种实例化对象的方式:
template <typename T> QDataStream &readObjectList(QDataStream &str,
QList<T*> &items)
quint32 itemCount;
str >> itemCount;
if (!itemCount) return str;
quint8 version;
str >> version;
if (version != 1)
str.setStatus(QDataStream::ReadCorruptData);
return str;
LoadCache strings;
for (; itemCount; itemCount--)
QByteArray string;
// Type
T *obj = ;
strings.load(str, string);
if (T::staticMetaObject.className() == string)
obj = new T();
else
string.append('*');
auto id = QMetaType::type(string);
const auto *mo = QMetaType::metaObjectForType(id);
if (mo)
obj = qobject_cast<T*>(mo->newInstance());
if (obj)
items << obj;
// Properties
quint32 propertyCount;
str >> propertyCount;
for (uint i = 0; i < propertyCount; ++i)
QVariant value;
strings.load(str, string) >> value;
if (obj) obj->setProperty(string, value);
return str;
还有一个非常简单的测试工具:
QDataStream &operator<<(QDataStream &str, const QList<CustomObject*> &items)
return writeObjectList(str, items);
QDataStream& operator>>(QDataStream &str, QList<CustomObject*> &items)
return readObjectList(str, items);
int main()
qRegisterMetaType<CustomObject*>(); // necessary for any classes derived from CustomObject*
QBuffer buf;
buf.open(QBuffer::ReadWrite);
QDataStream ds(&buf);
CustomObject obj;
obj.setObjectName("customsky");
obj.setProperty("prop", 20);
QList<CustomObject*> list;
list << &obj;
ds << list;
QList<CustomObject*> list2;
buf.seek(0);
ds >> list2;
Q_ASSERT(list2.size() == list.size());
for (int i = 0; i < list.size(); ++i)
auto *obj1 = list.at(i);
auto *obj2 = list2.at(i);
Q_ASSERT(obj1->objectName() == obj2->objectName());
Q_ASSERT(obj1->dynamicPropertyNames() == obj2->dynamicPropertyNames());
for (auto &name : obj1->dynamicPropertyNames())
Q_ASSERT(obj1->property(name) == obj2->property(name));
#include "main.moc"
【讨论】:
您的代码工作正常,但如果我理解得很好,它不会按原样保存 QList,而是将其分解为简单的元素,例如这个幼稚的版本: outStream 它分解QList
的原因是流式传输列表本身的运算符过于笼统,无法防止输出中可能出现大量冗余。到目前为止,我的代码仅将每个属性名称存储一次。可以想象进一步将面向字节的霍夫曼编码应用于属性名称索引,以临时存储霍夫曼表为代价进一步缩短序列化表示。
QDataStream & operator<<(QDataStream&, const QList&)
的通用性是有代价的。你应该在有意义的时候重用代码。在这里,重用该运算符没有什么意义。请记住,QList
特定的流操作符并没有什么特别之处。这并不是说它以某种方式序列化了存储 QList
的事实等。列表的序列化表示只是一个计数,后跟列表元素的序列化表示,从索引零开始依次上升。
所以,你注意到的是真实的,但没有理由担心。您使用它的方式仍然是您只需按原样流式传输列表 - 但使用我们的自定义运算符而不是默认运算符。我们所做的只是替换一个不同的运算符:我们不是替换CustomObject*
-specific 运算符,而是替换QList<CustomObject*>
-specific 运算符。以上是关于无法从文件中读取 QList<Class*>的主要内容,如果未能解决你的问题,请参考以下文章