如何使用QDatastream在QT中正确序列化和反序列化QList类?

时间:2018-07-18 13:34:51

标签: c++ qt serialization qdatastream

我正在尝试序列化自定义类Layer*并使用QDataStream读回。现在,Layer是一个带有虚拟方法的抽象类,它由RasterLayerTextLayerAdjustmentLayer等不同种类的层继承。

我有一个QList<Layer*> layers,它可以跟踪所有图层,并且对图层所做的任何调整都会在列表中进行更新。我需要将QList序列化和反序列化为其原始状态,并恢复(不同类型的)各个层的属性。

这是 layer.h

#ifndef LAYER_H
#define LAYER_H

#include <QString>
#include <QImage>
#include <QDebug>
#include <QListWidgetItem>
#include <QGraphicsItem>
#include <QPixmap>

class Layer : public QListWidgetItem
{

public:

    enum LayerType{
        RASTER,
        VECTOR,
        TEXT,
        ADJUSTMENT
    };

    Layer(QString name, LayerType type);

    ~Layer();
    inline void setName(QString &name) { _name = name; }
    inline QString getName() { return _name; }
    inline LayerType getType() { return _type; }

    virtual void setSceneSelected(bool select) = 0;
    virtual void setLayerSelected(bool select) = 0;
    virtual void setZvalue(int z) = 0;
    virtual void setParent(QGraphicsItem *parent) = 0;

protected:
    QString _name;
    LayerType _type;
};

#endif // LAYER_H

这由 RasterLayer 类扩展:

#ifndef RASTERLAYER_H
#define RASTERLAYER_H

#include <QGraphicsPixmapItem>
#include <QPainter>
#include <QGraphicsScene>

#include "layer.h"

    class RasterLayer : public Layer, public QGraphicsPixmapItem
    {
    public:
        RasterLayer(const QString &name, const QImage &image);
        RasterLayer();
        ~RasterLayer();

        void setLocked(bool lock);
        void setSceneSelected(bool select);
        void setLayerSelected(bool select);
        void setZvalue(int z);
        void setParent(QGraphicsItem *parent);
        inline QPixmap getPixmap() const { return pixmap(); }
        inline QPointF getPos() const { return QGraphicsPixmapItem::pos(); }
        inline void setLayerPos(QPointF pos) { setPos(pos);}
        inline void setLayerPixmap(QPixmap pixmap) { setPixmap(pixmap); }

        friend QDataStream& operator<<(QDataStream& ds, RasterLayer *&layer)
        {
            ds << layer->getPixmap() << layer->getName() << layer->getPos();
            return ds;
        }

        friend QDataStream& operator>>(QDataStream& ds, RasterLayer *layer)
        {
            QString name;
            QPixmap pixmap;
            QPointF pos;

            ds >> pixmap >> name >> pos;

            layer->setName(name);
            layer->setPixmap(pixmap);
            layer->setPos(pos);

            return ds;
        }

    protected:
        void paint(QPainter *painter,
                   const QStyleOptionGraphicsItem *option,
                   QWidget *widget);

    private:
        QImage _image;
    };

    #endif // RASTERLAYER_H

我目前正在尝试像这样测试RasterLayer的序列化/反序列化:

QFile file(fileName);

file.open(QIODevice::WriteOnly);
QDataStream out(&file);

Layer *layer = paintWidget->getItems().at(1);
// Gets the second element in the list

RasterLayer *raster = dynamic_cast<RasterLayer*> (layer);
out << raster;
file.close();

现在,如您在此处看到的,我专门将Layer*强制转换为RasterLayer*进行序列化,并且此方法行之有效,因为到目前为止我只处理一种类型的图层。所以我的第一个问题是:

如何将序列化过程推广到所有类型的层?

每种类型的层都有不同的序列化方式,因为每种层具有不同的属性。另外,这里的转换感觉有点代码味道,并且可能是错误的设计选择。因此,可以通过序列化整个层列表来调用其相应的重载运算符之类的方法来实现。

我的第二个问题是:

如何正确反序列化数据? 这是我当前序列化单个RasterLayer的方式:

QFile newFile(fileName);
newFile.open(QIODevice::ReadOnly);
QDataStream in(&newFile);

RasterLayer *layer2 = new RasterLayer;
in >> layer2;
paintWidget->pushLayer(layer2);
ui->layerView->updateItems(paintWidget->getItems());

首先,在这种情况下,我不认为序列化到指针是我应该做的事情,但是我不确定还有什么要做或如何做得更好。其次,反序列化在这里起作用,但是并不能完全满足我的期望。尽管我在重载运算符中使用了setter,但实际上并没有正确地更新图层。我需要调用构造函数以在其中新建一个层。

我已经尝试过:Serialization with Qt,但是我不太确定如何将Layer*转换为Layer,对其进行序列化,反序列化然后将其转换回{ {1}}。 所以我需要添加第三步:

Layer*

,然后将RasterLayer *layer3 = new RasterLayer(layer2->getName(), layer2->getPixmap().toImage()); layer3->setPos(layer2->pos()); 推入列表以使其真正起作用。根据这篇帖子:https://stackoverflow.com/a/23697747/6109408,我真的不应该在运算符重载函数中进行layer3的操作(否则我会在地狱里炸),我正在遵循那里给出的第一个建议,即就我而言,还不是很有效,我也不知道正确的方法。

此外,如何为new RasterLayer... s的常规QList进行反序列化,而不必创建新的特定层实例并向其注入反序列化的数据?尽管这是相似的:Serialize a class with a Qlist of custom classes as member (using QDataStream),但答案还不够清楚,我无法理解。

我对中间值持有者类有一个想法,我将使用它来序列化所有类型的Layers,并根据其类型创建并注入参数,但是我不确定这是否可行

感谢您的帮助。

2 个答案:

答案 0 :(得分:3)

我希望以下示例能为您提供总体思路:

#include <iostream>
#include <fstream>
#include <list>

class A{
    int a=0;
public:
    virtual int type(){return 0;}
    virtual void serialize(std::ostream& stream)const{
        stream<<a<<std::endl;
    }
    virtual void deserialize(std::istream& stream){
        stream>>a;
    }

    friend std::ostream& operator <<(std::ostream& stream, const A& object){
        object.serialize(stream);
        return stream;
    }
    friend std::istream& operator >>(std::istream& stream, A& object){
        object.deserialize(stream);
        return stream;
    }

    virtual ~A(){}
};

class B : public A{
  int b=1;
public:
  virtual int type(){return 1;}
  virtual void serialize(std::ostream& stream)const{
      A::serialize(stream);
      stream<<b<<std::endl;
  }
  virtual void deserialize(std::istream& stream){
      A::deserialize(stream);
      stream>>b;
  }
};

class C : public A{
  int c=2;
public:
  virtual int type(){return 2;}
  virtual void serialize(std::ostream& stream)const{
      A::serialize(stream);
      stream<<c<<std::endl;
  }
  virtual void deserialize(std::istream& stream){
      A::deserialize(stream);
      stream>>c;
  }
};

std::ostream& operator <<(std::ostream& stream, const std::list<A*>& l){
    stream<<l.size()<<std::endl;
    for(auto& a_ptr: l){
        stream<<a_ptr->type()<<std::endl;
        stream<<*a_ptr;
    }
}
std::istream& operator >>(std::istream& stream, std::list<A*>& l){
    l.clear();
    int size, type;
    stream>>size;
    A* tmp;
    for(int i =0; i<size; ++i){
        stream>>type;
        if(type==0){
           tmp = new A;
        }
        if(type==1){
           tmp = new B;
        }
        if(type==2){
           tmp = new C;
        }
        stream>>(*tmp);
        l.push_back(tmp);
    }
    return stream;
}


int main(){
    A* a = new A;
    A* b = new B;
    A* c = new C;
    std::list<A*> List{ a, b, c };
    std::list<A*> List2;
    std::ofstream ofs("D:\\temp.txt");
    ofs<<List;
    ofs.flush();
    ofs.close();

    std::ifstream ifs("D:\\temp.txt");
    ifs>>List2;
    std::cout<<List2;
    for(auto& a_ptr : List2){
        delete a_ptr;
    }
    delete c;
    delete b;
    delete a;
    return 0;
}

编辑:当时我没有考虑过这样的事实,即在序列化列表时,我们应该写出列表的大小和元素类型以实现完全反序列化,所以我修改了示例。

答案 1 :(得分:3)

满足您的需求:典型的方法是利用多态性。

基类(QListWidgetItem)具有执行序列化和反序列化的接口。我们可以利用它来实现对派生类型的指针的(反)序列化。序列化调用在派生类中实现的接口以序列化派生特定的数据。反序列化首先使用特定于类型的工厂来创建派生类型的实例,然后才使用基类的运算符调用在派生类中实现的反序列化接口。

一旦实现了基本类型的序列化和反序列化,QListQVariant(!)也会正常工作。

您不应该实现自己的类型存储-QListWidgetItem已经为您提供了它!

Layer类是从QGraphicsItem派生的类的抽象基础。 typeId()typeName()方法利用了元类型类型系统。派生类应将typeId(而不是type()!)传递给Layer的构造函数。

// https://github.com/KubaO/stackoverflown/tree/master/questions/stream-qwidgetlistitem-51403419
#include <QtWidgets>

class Layer : public QListWidgetItem {
public:
   virtual QGraphicsItem *it() = 0;
   const QGraphicsItem *it() const { return const_cast<Layer*>(this)->it(); }
   int typeId() const {
      if (type() < UserType)
         return QMetaType::UnknownType;
      return type() - QListWidgetItem::UserType + QMetaType::User;
   }
   const char *typeName() const { return QMetaType::typeName(typeId()); }
   void write(QDataStream&) const override;
   void read(QDataStream&) override;
   QListWidgetItem *clone() const override final;

   void setZValue(int z) { it()->setZValue(z); }
   void setParentItem(Layer *parent) { it()->setParentItem(parent->it()); }
   void setParentItem(QGraphicsItem *parent) { it()->setParentItem(parent); }
   void setSelected(bool sel) { it()->setSelected(sel); }
   void setPos(const QPointF &pos) { it()->setPos(pos); }

   Layer(const Layer &);
   QString name() const { return m_name; }
   void setName(const QString &n) { m_name = n; }
   ~Layer() override = default;
protected:
   using Format = quint8;
   Layer(const QString &name, int typeId);
   static void invalidFormat(QDataStream &);
   template <typename T> T &assign(const T& o) { return static_cast<T&>(assignLayer(o)); }
private:
   QString m_name;
   Layer& assignLayer(const Layer &);
};

it()帮助器提供对派生的QGraphicsItem*类型的访问。实现的基础比较简单。

Layer::Layer(const Layer &o) : Layer(o.name(), o.typeId()) {}

Layer::Layer(const QString &name, int typeId) :
   QListWidgetItem(nullptr, typeId - QMetaType::User + QListWidgetItem::UserType),
   m_name(name)
{}

QListWidgetItem *Layer::clone() const {
   const QMetaType mt(typeId());
   Q_ASSERT(mt.isValid());
   return reinterpret_cast<QListWidgetItem*>(mt.create(this));
}

Layer &Layer::assignLayer(const Layer &o) {
   Q_ASSERT(o.type() == type());
   const QMetaType mt(typeId());
   Q_ASSERT(mt.isValid());
   this->~Layer();
   mt.construct(this, &o);
   return *this;
}

对数据进行版本控制以确保向后兼容很重要:软件的较新版本应能够读取由较旧版本编写的数据。因此,每个类都维护自己的格式指示符。这将Layer类的格式与派生类的格式分离。数据类型另存为文本,以确保可移植性,因为可能会更改类型ID。

void Layer::write(QDataStream &ds) const {
   ds << typeName() << (Format)0 << m_name << it()->pos();
   QListWidgetItem::write(ds);
}

void Layer::read(QDataStream &ds) {
   QByteArray typeName_;
   Format format_;
   QPointF pos_;
   ds >> typeName_ >> format_;
   if (typeName_.endsWith('\0')) typeName_.chop(1);
   Q_ASSERT(typeName_ == typeName());
   if (format_ >= 0) {
      ds >> m_name >> pos_;
      setPos(pos_);
      QListWidgetItem::read(ds);
   }
   if (format_ >= 1)
      invalidFormat(ds);
}

void Layer::invalidFormat(QDataStream &ds) {
   ds.setStatus(QDataStream::ReadCorruptData);
}

Qt已经提供了流运算符来引用QListWidgetItem。我们需要提供处理该类型的指针的流运算符。输出运算符立即转发到参考输出运算符。输入运算符会窥视存储在流中的对象的类型,使用该类型来查找元类型id,然后使用QMetaType::create()对其进行实例化。然后,它转发给引用参考输入运算符。

QDataStream &operator<<(QDataStream &ds, const Layer *l) {
   return ds << *l;
}

QByteArray peekByteArray(QDataStream &ds) {
   qint32 size;
   auto read = ds.device()->peek(reinterpret_cast<char*>(&size), sizeof(size));
   if (read != sizeof(size))
      return ds.setStatus(QDataStream::ReadPastEnd), QByteArray();
   if (ds.byteOrder() == QDataStream::BigEndian)
      size = qFromBigEndian(size);
   auto buf = ds.device()->peek(size + 4);
   if (buf.size() != size + 4)
      return ds.setStatus(QDataStream::ReadPastEnd), QByteArray();
   if (buf.endsWith('\0')) buf.chop(1);
   return buf.mid(4);
}

QDataStream &operator>>(QDataStream &ds, Layer *&l) {
   auto typeName = peekByteArray(ds);
   int typeId = QMetaType::type(typeName);
   QMetaType mt(typeId);
   l = mt.isValid() ? reinterpret_cast<Layer*>(mt.create()) : nullptr;
   if (l)
      ds >> *l;
   else
      ds.setStatus(QDataStream::ReadCorruptData);
   return ds;
}

一旦建立了Layer抽象基类,就可以轻松实现派生类:

class RasterLayer : public Layer, public QGraphicsPixmapItem {
public:
   QGraphicsItem *it() override { return this; }
   int type() const override { return Layer::type(); }
   RasterLayer &operator=(const RasterLayer &o) { return assign(o); }
   void write(QDataStream &) const override;
   void read(QDataStream &) override;
   RasterLayer(const RasterLayer &);
   RasterLayer(const QString &name = {});
};
Q_DECLARE_METATYPE(RasterLayer)

// implementation

static int rasterOps = qRegisterMetaTypeStreamOperators<RasterLayer>();

RasterLayer::RasterLayer(const RasterLayer &o) :
   Layer(o),
   QGraphicsPixmapItem(o.pixmap())
{}

RasterLayer::RasterLayer(const QString &name) : Layer(name, qMetaTypeId<RasterLayer>()) {}

void RasterLayer::write(QDataStream &ds) const {
   Layer::write(ds);
   ds << Format(0) << pixmap();
}

void RasterLayer::read(QDataStream &ds) {
   Layer::read(ds);
   Format format_;
   QPixmap pix_;
   ds >> format_;
   if (format_ >= 0) {
      ds >> pix_;
      setPixmap(pix_);
   }
   if (format_ >= 1)
      invalidFormat(ds);
}

而且,类似地:

class VectorLayer : public Layer, public QGraphicsPathItem {
public:
   QGraphicsItem *it() override { return this; }
   int type() const override { return Layer::type(); }
   VectorLayer &operator=(const VectorLayer &o) { return assign(o); }
   void write(QDataStream &) const override;
   void read(QDataStream &) override;
   VectorLayer(const VectorLayer &);
   VectorLayer(const QString &name = {});
};
Q_DECLARE_METATYPE(VectorLayer)

// implementation

static int vectorOps = qRegisterMetaTypeStreamOperators<VectorLayer>();

VectorLayer::VectorLayer(const VectorLayer &o) :
   Layer(o),
   QGraphicsPathItem(o.path())
{}

VectorLayer::VectorLayer(const QString &name) : Layer(name, qMetaTypeId<VectorLayer>()) {}

void VectorLayer::write(QDataStream &ds) const {
   Layer::write(ds);
   ds << Format(0) << path();
}

void VectorLayer::read(QDataStream &ds) {
   Layer::read(ds);
   Format format_;
   QPainterPath path_;
   ds >> format_;
   if (format_ >= 0) {
      ds >> path_;
      setPath(path_);
   }
   if (format_ >= 1)
      invalidFormat(ds);
}

rasterOpsvectorOps是伪变量,用于为输入main()之前的类型注册流运算符。他们没有其他目的。这些流运算符注册用于将类型与QVector接口。

现在我们可以编写一个测试工具,演示支持的流操作。

#include <QtTest>

class LayerTest : public QObject {
   Q_OBJECT
   QBuffer buf;
   QDataStream ds{&buf};

private slots:
   void initTestCase() {
      buf.open(QIODevice::ReadWrite);
   }

   void testClone() {
      RasterLayer raster("foo");
      QScopedPointer<QListWidgetItem> clone(raster.clone());
      auto *raster2 = static_cast<RasterLayer*>(clone.data());

      QCOMPARE(raster2->type(), raster.type());
      QCOMPARE(raster2->name(), raster.name());
   }

   void testValueIO() {
      ds.device()->reset();
      RasterLayer raster("foo");
      VectorLayer vector("bar");
      ds << raster << vector;

      ds.device()->reset();
      RasterLayer raster2;
      VectorLayer vector2;
      ds >> raster2 >> vector2;

      QCOMPARE(raster2.name(), raster.name());
      QCOMPARE(vector2.name(), vector.name());
   }

   void testPointerIO() {
      ds.device()->reset();
      RasterLayer raster("foo");
      VectorLayer vector("bar");
      ds << &raster << &vector;

      ds.device()->reset();
      Layer *raster2 = {}, *vector2 = {};
      ds >> raster2 >> vector2;

      QVERIFY(raster2 && vector2);
      QCOMPARE(raster2->typeId(), qMetaTypeId<RasterLayer>());
      QCOMPARE(vector2->typeId(), qMetaTypeId<VectorLayer>());
      QCOMPARE(raster2->name(), raster.name());
      QCOMPARE(vector2->name(), vector.name());
      delete raster2;
      delete vector2;
   }

   void testValueContainerIO() {
      ds.device()->reset();
      QVector<RasterLayer> rasters(2);
      QList<VectorLayer> vectors;
      vectors << VectorLayer() << VectorLayer();
      ds << rasters << vectors;

      ds.device()->reset();
      rasters.clear();
      vectors.clear();
      ds >> rasters >> vectors;

      QCOMPARE(rasters.size(), 2);
      QCOMPARE(vectors.size(), 2);
   }

   void testPointerConteinerIO() {
      ds.device()->reset();
      RasterLayer raster;
      VectorLayer vector;
      QList<Layer*> layers;
      layers << &raster << &vector;
      ds << layers;

      ds.device()->reset();
      layers.clear();
      QVERIFY(layers.isEmpty());
      ds >> layers;
      QCOMPARE(layers.size(), 2);
      QVERIFY(!layers.contains({}));
      qDeleteAll(layers);
   }

   void testVariantIO() {
      ds.device()->reset();
      RasterLayer raster;
      VectorLayer vector;
      auto vr = QVariant::fromValue(raster);
      auto vv = QVariant::fromValue(vector);
      ds << vr << vv;

      ds.device()->reset();
      vv.clear();
      vr.clear();
      QVERIFY(vr.isNull() && vv.isNull());
      ds >> vr >> vv;
      QVERIFY(!vr.isNull() && !vv.isNull());
      QCOMPARE(vr.userType(), qMetaTypeId<RasterLayer>());
      QCOMPARE(vv.userType(), qMetaTypeId<VectorLayer>());
   }

   void testVariantContainerIO() {
      ds.device()->reset();
      QVariantList layers;
      layers << QVariant::fromValue(RasterLayer())
             << QVariant::fromValue(VectorLayer());
      ds << layers;

      ds.device()->reset();
      layers.clear();
      ds >> layers;
      QCOMPARE(layers.size(), 2);
      QVERIFY(!layers.contains({}));
      QCOMPARE(layers.at(0).userType(), qMetaTypeId<RasterLayer>());
      QCOMPARE(layers.at(1).userType(), qMetaTypeId<VectorLayer>());
   }
};

QTEST_MAIN(LayerTest)
#include "main.moc"

到此结束了完整的可编译示例。