3

我正在尝试序列化自定义类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),答案不够清楚,我无法理解。

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

谢谢你的协助。

4

2 回答 2

4

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

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

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

于 2018-07-18T14:13:45.053 回答
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"

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

于 2018-07-18T15:30:29.380 回答