0

我对流运算符>>有疑问。我正在尝试将自定义对象的 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<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();

应用程序在 operator>> 中 for 循环的条件语句处出现段错误:

i < item->metaObject()->propertyCount()

item不可访问。

你能解释一下错误在哪里吗?

很感谢。

4

2 回答 2

2

您的代码正在崩溃,因为在您的输入运算符中,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;
}
于 2014-06-12T16:14:15.073 回答
1

您的代码段错误,因为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"
于 2014-06-12T15:59:39.747 回答