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

标签 c++ qt serialization qdatastream

我正在尝试序列化自定义类 Layer*并使用 QDataStream 读回。现在,Layer是一个具有虚方法的抽象类,由不同类型的层继承:RasterLayer , TextLayer , AdjustmentLayer

我有一个 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 ,对其进行序列化、反序列化,然后将其转换回 Layer* . 所以我需要添加第三步:

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

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

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

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

谢谢你帮助我。

最佳答案

我希望下面的例子能给你一个大概的概念:

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

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

关于c++ - 如何使用 QDatastream 在 QT 中正确序列化和反序列化 QList 类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51403419/

相关文章:

c++ - 如何在属性表中捕获 "tab changed"事件

c++ - 从 Eigen::Map 构造 Eigen::Array,它是如何工作的?

c++ - 对 cv::calcOpticalFlowPyrLK 的 undefined reference

javascript - 使用 jQuery 将表单数据转换为 JavaScript 对象

java - Java中如何实现更灵活的序列化和反序列化?

c# - 更改 Internet Explorer 安全设置(初始化和脚本 activex 控件不裸...)

c++ - Oculus Tiny Room,Directx,将 3D 模型加载到场景中

c++ - DLL函数调用问题如何解决

qt - 防止 QGraphicsItem 中的字体缩放

jquery - 将记录转换为序列化表单数据以通过 HTTP 发送