【问题标题】:Can't read QList<Class*> from a file无法从文件中读取 QList<Class*>
【发布时间】: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

标签: qt qfile qdatastream


【解决方案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/stackoverflown/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 运算符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-28
  • 2020-04-07
  • 1970-01-01
  • 2017-09-06
相关资源
最近更新 更多