【问题标题】:How to load content of a const variable from a file?如何从文件中加载 const 变量的内容?
【发布时间】:2019-07-16 03:27:02
【问题描述】:

具有 const 成员变量的对象的内容保存和检索(到磁盘文件/从磁盘文件)的方法是什么?
或者更具体地说, const 成员要求在对象创建时进行初始化。因此,内容的检索必须发生在初始化程序之前(在构造函数的 { } 之前)。如果我们不介意封装,我们可以检索并创建带有参数的对象。如何通过保持数据隐藏来做所有事情?


编译器:C++ 14 甚至更高版本。


对象的实例化,填充内容并为下一个上下文存储。

    { //CODE BLOCK 1 : making of content and saving to a diskfile
        Abcd abcd(65535,256,25);
        //some operations on abcd
        //save to disk
        QFile abcdFile("abcd.lion");
        abcdFile.open(QFile::WriteOnly);
        abcd.serialize(abcdFile);
        abcdFile.close();
    }

从文件中获取后使用相同的对象。

    { //CODE BLOCK 2 : loading from file and continue in another context
        QFile abcdFile("abcd.lion");
        abcdFile.open(QFile::ReadOnly);
        Abcd abcdNew(abcdFile);
        abcdFile.close();
        if(!abcdNew.isHealthy())
            printf("abcd from hdd is NOT Healthy :(\n");
        else
        {
            //doTheJob(abcdNew);
        }
    }

班级。

#include <QFile>
class Abcd
{
    const bool _healthy;//true if properly initialized
    //IMPORTANT: _healthy has to be the first member in the class.
    //this is to execute its initializer list first
protected:
    const long _rX;
    const long _rY;
          long _count;
public:
    Abcd(const long refX,
         const long refY,
         const long count) :
        _healthy(true),
        _rX(refX), _rY(refY),
        _count(count)
    {

    }
    Abcd(QFile &src) :
        _healthy(deserialize(src)),
        //Hack. Actually the initialization happened by this statement.
        //just keeping the below statements for the sake of syntactical correctness. :(
        _rX(_rX), _rY(_rY)
        //,_count(count)
    {

    }
    virtual
    ~Abcd()
    {

    }
    inline
    bool isHealthy()
    {
        return _healthy;
    }
    bool serialize(QFile &dest)
    {
        if(dest.write((char *)&_rY,sizeof(_rY))!=sizeof(_rY)) return false;
        if(dest.write((char *)&_rX,sizeof(_rX))!=sizeof(_rX)) return false;
        if(dest.write((char *)&_count,sizeof(_count))!=sizeof(_count)) return false;
        return true;
    }
private:
    bool deserialize(QFile &src)
    {
        if(src.read((char *)&_rY,sizeof(_rY))!=sizeof(_rY)) return false;
        if(src.read((char *)&_rX,sizeof(_rX))!=sizeof(_rX)) return false;
        if(src.read((char *)&_count,sizeof(_count))!=sizeof(_count)) return false;
        return true;
   }
};

请提出更好的方法。为此,我在类的声明中引入了一个“健康”状态成员作为第一个成员。同样在反序列化中,我通过将 const 变量转换为 char * 指针来愚弄编译器。

【问题讨论】:

  • 想了一个结论。我有两个解决方案。 1. deserialize() 作为使用 RVO 和异常错误报告的静态函数。 2. 每种媒体(如磁盘文件、云等)的单独保存/检索对象。我为我的目的使用了第一个。由于在我的特定编译器的调试版本中未启用 RVO,因此创建了一个复制构造函数。

标签: c++ constructor c++14 deserialization constants


【解决方案1】:

我的建议是将Abcd和序列化/反序列化的逻辑拆分为两个类。

好处:

  • 没有字段_healthy 作为对象在设计上始终有效。
  • class Abcd 只做一项工作。没有任何存储逻辑(单一职责)

一些提示:

  • 自 c++ 17 起,RVO 是强制性的
  • const 字段只是使对象不可复制/移动分配(无法与容器等一起使用)。只需正确使用 const 正确性即可确保不变性。
  • 不继承实现,只有接口:Abcd 是最终的,没有虚拟方法 - 性能更好。
  • 关注Cpp Core Guidelines
class Abcd final
{
public:
    Abcd(const long refX, const long refY, const long count)
        : _rX(refX)
        , _rY(refY)
        , _count(count)
    {
    }

    long GetRX() const
    {
        return _rX;
    }

    long GetRY() const
    {
        return _rY;
    }

    long GetCount() const
    {
        return _count;
    }

protected:
    long _rX;
    long _rY;
    long _count;
};
#include <boost/optional.hpp>
#include <QFile>

template <typename T>
using Opt = boost::optional<T>; // or equivalent

// Choose better name for Serializer or even split it up
class AbcdSerializer final
{
public:
    AbcdSerializer(QFile& file)
        : _file(file)
    {
    }

    // You may also throw an exception instead of returning optional
    Opt<Abcd> TryDeserializeAbcd()
    {
        long rX;
        long rY;
        long count;

        if (ReadValue(rY) && ReadValue(rX) && ReadValue(count))
        {
            return Abcd(rX, rY, count);
        }

        return {};
    }

    bool SerializeAbcd(const Abcd& abcd)
    {
        return WriteValue(abcd.GetRY()) && WriteValue(abcd.GetRX()) && WriteValue(abcd.GetCount());
    }

private:
    template <typename T>
    bool ReadValue(T& value)
    {
        constexpr auto ValueSize = sizeof(value);

        return _file.read(reinterpret_cast<char*>(&value), ValueSize) != ValueSize;
    }

    template <typename T>
    bool WriteValue(const T& value)
    {
        constexpr auto ValueSize = sizeof(value);

        return _file.write(reinterpret_cast<const char*>(&value), ValueSize) != ValueSize;
    }

    QFile& _file;
};

【讨论】:

  • 感谢您的思考过程和为回复所付出的努力。几乎,完全重写!我不想转到与存储相关的不同讨论。所以只使用了QFile。为了映射到不同的数据源/目标,自然的选择是streams。然后我们可以将存储指向磁盘文件、内存或云存储。另外,我想保持我的对象自包含,而不是为所有内容提供 get/set 函数。当然,它可能不是每个人的选择。无论如何,非常感谢您的写作。另外,我没有使用 boost。这对我来说是新的。
  • Qt 有QVariant,c++17 以后有std::optional
  • 你也可以使用friend——那么就不需要getter了。 Setter(在普通对象状态操作的意义上)只是破坏了封装——所以我根本没有提到它们;)。
  • 哈哈...我无法想象交友。无论如何得到你的朋友..再次感谢。
【解决方案2】:

我的建议是使用类的static 成员函数从磁盘中检索文件的内容,并在成功检索到内容后构造一个对象。

代替:

 Abcd(QFile &src) :

使用

static Abcd deserialize(QFile& src);

并将其实现为:

Abcd Abcd::deserialize(QFile& src)
{
   long rX;
   long rY;
   long count;

   if(src.read((char *)&rY, sizeof(rY)) != sizeof(rY)) throw false;
   if(src.read((char *)&rX, sizeof(rX)) != sizeof(rX)) throw false;
   if(src.read((char *)&count, sizeof(count)) != sizeof(count)) throw false;

   return Abcd(rX, rY, count):
}

PS奇怪的是你先存_rY再存_rX。没有错,就是奇怪。

【讨论】:

  • 欣赏这种方法。但它会导致创建两个对象。对吗?
  • 如果编译器支持返回值优化 (RVO),那么它不会。不仅如此,标准的较新版本之一,不确定是C++14还是C++17,需要RVO。
  • @veenusAV,除非性能是一个经过验证的问题,否则我不会担心。
  • 好的。谢谢你。这是一个解决方案。希望 RVO 能够在我们所有的平台上运行。然后关于_rY和_rX,我正在写一个网格。因此,首选行和列。它显示了你对细节的关注。不错
  • 我采用了这个解决方案。然后发现:默认的DEBUG构建配置,是不支持RVO的。发布工作正常。所以我写了一个复制构造函数。 config Windows,Qt 创建者,MS Visual 2015 编译器。这可能是因为没有更新新 MSVC 的二进制文件。
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 2010-09-13
  • 2013-07-31
  • 1970-01-01
  • 2022-10-17
相关资源
最近更新 更多