首页 > 解决方案 > 如何使用 QDatastream 在 QT 中正确序列化和反序列化 QList 类?

问题描述

我正在尝试序列化自定义类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*要序列化的 a,这很有效,因为到目前为止我只处理了一种类型的层。所以我的第一个问题是:

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

每种类型的层都有不同的序列化方式,因为每个层都有不同的属性。此外,这里的铸造感觉有点代码味道,可能是一个糟糕的设计选择。因此,将整个层列表序列化并调用其相应的重载运算符将是预期的场景。

我的第二个问题是:

如何正确反序列化数据? 这是我目前对个人进行序列化的方式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,但实际上并没有正确更新图层。我需要调用构造函数来创建一个新层。

我试过这个:使用 Qt 进行序列化,但我不太确定如何Layer*将其转换为Layer、序列化、反序列化,然后将其转换回Layer*. 所以我需要添加第三步:

RasterLayer *layer3 = new RasterLayer(layer2->getName(), layer2->getPixmap().toImage());
layer3->setPos(layer2->pos());

然后推layer3送到列表以使其真正起作用。根据这篇文章:https ://stackoverflow.com/a/23697747/6109408 ,我真的不应该new RasterLayer...在运算符重载函数内部做一个(否则我会在地狱里炸),我正在遵循给出的第一个建议那里,这对我来说不是很有效,我不知道正确的方法。

另外,我如何为Layer*s 的一般 QList 反序列化它,而不必创建新的特定层实例并将反序列化数据注入它们?虽然这很相似:Serialize a class with a Qlist of custom classes as member (using QDataStream),答案不够清楚,我无法理解。

我有一个关于中间值持有者类的想法,我将使用它来序列化各种层,并让它根据层的类型创建和注入参数,但我不确定这是否可行。

谢谢你的协助。

标签: c++qtserializationqdatastream

解决方案


我希望下面的例子能给你一个大致的想法:

#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;
}

编辑:在我没有考虑到序列化列表时我们应该为成功反序列化编写列表大小和元素类型的事实,所以我修改了示例。


推荐阅读