无法从文件中读取 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_objectsListQList&lt;CustomObject*&gt;

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 &amp; operator&lt;&lt;(QDataStream&amp;, const QList&amp;) 的通用性是有代价的。你应该在有意义的时候重用代码。在这里,重用该运算符没有什么意义。请记住,QList 特定的流操作符并没有什么特别之处。这并不是说它以某种方式序列化了存储 QList 的事实等。列表的序列化表示只是一个计数,后跟列表元素的序列化表示,从索引零开始依次上升。 所以,你注意到的是真实的,但没有理由担心。您使用它的方式仍然是您只需按原样流式传输列表 - 但使用我们的自定义运算符而不是默认运算符。我们所做的只是替换一个不同的运算符:我们不是替换CustomObject*-specific 运算符,而是替换QList&lt;CustomObject*&gt;-specific 运算符。

以上是关于无法从文件中读取 QList<Class*>的主要内容,如果未能解决你的问题,请参考以下文章

在 QML 中读取 QList<QPoint>

QList<Class*> 的 QDatastream 运算符>>

无法将 QList<QUrl> 分配给 QString

如何正确地将对象/指针存储到 Qlist 中

QT快速读取Excel

用于 QList 的 QSetting<数据类型>