存储包含 std::vector 且不含向量数据的结构

问题描述 投票:0回答:1

我有一个带有子结构的复杂结构来存储程序的设置。该结构体包含一些

std::vectors
。我用这个简单的函数保存结构:

void saveSettings(std::string filename)
{
    std::ofstream file(filename, std::ios::binary);
    if (file.is_open())
    {
        file.write(reinterpret_cast<const char*>(&Settings), sizeof(Settings));
        file.close();
    }
    else
        std::cout << "Error saving Settings.dat" << std::endl;
}

当程序加载时,它会读取设置文件并将数据再次存储在我的设置结构中。如果没有向量的话,这会很好地工作。导致问题的原因是向量的状态被恢复,包括数据指针,但数据数组本身不再存在。一旦我进行了调整大小或其他更改数组大小的操作,向量就会尝试删除旧的数据数组,这当然会失败(当然我无法读取/写入任何元素)。

我既不需要也不希望这些数据出现在我的设置文件中。我可以找到另一种方法来存储设置结构(没有向量),但我认为这会更加复杂并且会使其他事情出现问题。有没有办法“重置”向量或者给它一个有效的数据数组?

真正确实起作用的一件事是:

struct VectorHack
{
    size_t test1;
    size_t test2;
    size_t test3;
    size_t test4;
};

VectorHack& hackedVec = reinterpret_cast<VectorHack&>(vec);

//hackedVec.test1 = 0;
hackedVec.test2 = 0;
hackedVec.test3 = 0;
hackedVec.test4 = 0;

这似乎“重置”了向量。无论我是否将

test1
设置为 0,似乎都没有什么区别,但我不知道这个数字的用途(它是不同的,每次创建新向量时)。

当然,这是一个非常肮脏的黑客行为,可能会导致问题,并且可能无法移植。谁能向我指出一个更好、更简单的解决方案来“重置”向量或在没有向量的情况下存储结构?我尝试在存储向量之前清空它们(这似乎将数据指针设置为

nullptr
),但是一旦我调整大小,程序仍然崩溃。

c++ vector struct save
1个回答
0
投票

对于序列化-反序列化二进制文件。我们需要处理一些问题。

  • 内存对齐链接
  • 字节序链接
  • I/O 错误,尤其是读取文件时。

假设你的程序运行在Linux和Windows上,我们可以解决字节序问题。

如果你想保留你的脏方法,你必须将

std::vector
更改为固定大小(子设置的最大大小)数组,并将子设置的大小添加到设置中,然后你的脏方法应该可以工作。

如果您想要更好的方法将二进制数据保存到文件但不想使用通用格式,您需要设计文件中易于读取的结构数据

correctly

我可以给你看一些例子。

#include <inttypes.h>

#include <filesystem>
#include <fstream>
#include <iostream>
#include <vector>

struct SubSetting {
    int32_t a;
    uint64_t b;
};

struct Setting {
    int32_t x;
    uint64_t y;
    std::vector<SubSetting> subs;
};

void serialize(std::ostream& os, const SubSetting& sub) {
    // os.write(reinterpret_cast<const char*>(&sub), sizeof(SubSetting)); don't do this or we might write memory alignment
    os.write(reinterpret_cast<const char*>(&sub.a), sizeof(int32_t));
    os.write(reinterpret_cast<const char*>(&sub.b), sizeof(uint64_t));
    /**
     * SubSetting data | a = 4bytes | b = 8bytes | = 12bytes
     */
}

void unserialize(std::istream& is, SubSetting& sub) {
    is.read(reinterpret_cast<char*>(&sub.a), sizeof(int32_t))
        .read(reinterpret_cast<char*>(&sub.b), sizeof(uint64_t));
}

void serialize(std::ostream& os, const std::vector<SubSetting>& subs) {
    uint64_t size = subs.size();  // ensure my size is 8 bytes
    os.write(reinterpret_cast<char*>(&size), sizeof(uint64_t));  // it's much easier if we know size of element before read.
                                
    for (auto& sub : subs) {
        serialize(os, sub);
    }
    /**
     * std::vector<SubSetting> data | size of element = 8 bytes | SubSetting data 1 |  SubSetting data 2 |  SubSetting data ...
     */
}

void unserialize(std::istream& is, std::vector<SubSetting>& subs) {
    uint64_t size;
    is.read(reinterpret_cast<char*>(&size), sizeof(uint64_t));

    subs.reserve(size);  // prevert reallocate memory

    for (uint64_t i = 0; i < size; i++) {
        unserialize(is, subs.emplace_back());  // emplace_back() return ref in > c++17 only
    }
}

void serialize(std::ostream& os, const Setting& setting) {
    os.write(reinterpret_cast<const char*>(&setting.x), sizeof(int32_t));
    os.write(reinterpret_cast<const char*>(&setting.y), sizeof(uint64_t));
    serialize(os, setting.subs);
    /**
     * Setting data | x = 4bytes | y = 8bytes | std::vector<SubSetting>  data |
     */
}

void unserialize(std::istream& is, Setting& setting) {
    is.read(reinterpret_cast<char*>(&setting.x), sizeof(int32_t))
        .read(reinterpret_cast<char*>(&setting.y), sizeof(uint64_t));

    unserialize(is, setting.subs);
}

void print_setting(const Setting& setting) {
    std::cout << "setting: x=" << setting.x << " y=" << setting.y << "\n"
              << "sub setting\n";
    for (auto& sub : setting.subs) {
        std::cout << "a=" << sub.a << " b=" << sub.b << "\n";
    }
}

void save_setting(const std::filesystem::path& path,  const Setting& setting){
    std::ofstream ofs(path, std::ios::binary);
    serialize(ofs, setting);
}

int load_setting(const std::filesystem::path& path, Setting& to){
    std::ifstream ifs(path, std::ios::binary);
    unserialize(ifs, to);

    return 0;//error code 0 =  no error  
}

int main() {
    auto temp_file = std::filesystem::temp_directory_path();
    temp_file /= "setting.temp";

    Setting setting{};
    setting.x = 7;
    setting.y = 8;
    setting.subs.emplace_back(SubSetting{11, 22});
    setting.subs.emplace_back(SubSetting{33, 44});
    setting.subs.emplace_back(SubSetting{55, 66});

    save_setting(temp_file, setting);

    Setting loaded_setting{};

    auto ec = load_setting(temp_file, loaded_setting);

    if(ec){
        std::cerr << "load setting fail.\n";
        return 0;
    }

    print_setting(loaded_setting);
}

神箭

程序注释中有说明

© www.soinside.com 2019 - 2024. All rights reserved.