Запись и чтение вектора объектов класса в файл

216
27 мая 2018, 13:30

Возникла проблема. Я изучил абсолютно все, что смог найти. Я перечитал десятки тем на stacoverflow, как на русскоязычном, так и на англоязычном. Но все, что мне удалось найти либо не работает (по причине отличия моего случае от случая в теме), либо вообще не из той оперы. Позвольте я опишу цель и что я уже предпринимал для ее достижения. Сразу уточню, я не в коем случае не прошу писать за меня код, мне просто нужен совет, направление, как именно реализовать то, что мне нужно (какими методами), а их поиском я займусь сам. Итак, у меня есть класс

class Legal
{
private:
std::string name;
std::string phone;
std::string address;
std::string date;
std::string ogrn;
public:
Legal(
    std::string name = "Название не указано",
    std::string phone = "Номер не указан",
    std::string address = "Адрес регистрации не указан",
    std::string date = "Дата основания не указана",
    std::string ogrn = "ЕГРН не указан"
    ) {
        this->name = name;
        this->phone = phone;
        this->address = address;
        this->date = date;
        this->ogrn = ogrn;
    }
void setName(std::string& name) { this->name = name; }
void setPhone(std::string& phone) { this->phone = phone; }
void setAddress(std::string& address) { this->address = address; }
void setDate(std::string& date) { this->date = date; }
void setOgrn(std::string& ogrn) { this->ogrn = ogrn; }
std::string getName() { return name; }
};

Далее, я создаю vector vecLegal, и с пользователь может создавать новых "клиентов" заполняя объекты этого класса. В итоге получается вектор этих самых объектов, в которых сохраняется информация о "клиентах". Мне нужно, чтобы по итого работы программы все эти объекты из вектора сохранились в файл (txt, bin - не важно) и потом могли из него считываться. Своеобразная база данных. Я пробовал перегружать оператор <<, но в тех примерах экземпляры класса были типов int и у меня возникали проблемы с string. Я пробовал делаться сериализацию с помощью boost, но в этом я не силен и не понял почему не заработало. Лучшее чего мне удалось достичь, это

ofstream out_file("vector.bin", ios::binary | ios::out);
out_file.write((const char*)&vecToadd.front(), vecToadd.size()*sizeof(Legal));

Этими строками успешно создается файл vector.bin, но при каких либо попытках его считать в вектор я получал "Ошибка сегментирования(дамп памяти сброшен на диск)". Подскажите пожалуйста, что делаю не так и какие шаги стоит предпринять для решения моей проблемы?

Answer 1

Вы как минимум не читали этот сайт - тут столько раз говорилось о том, как быть с такими не-POD объектами, что лично мне уже набило оскомину...

Вашим способом вы записываете не содержимое строк в вашем объекте, а их служебные поля. Строка содержит в себе указатель на выделенную где-то память, в которой содержатся интересующая вас информация. Но вы пытаетесь писать просто эти указатели и другие служебные поля...

Получается примерно так - жена говорит собраться в отпуск и в машину в багажник сложить, ну, там, матрас надувной, палатку, мангал и шампуры - ну, в общем, барахло. Вы в багажник кладете бумажки с надписями "Матрас - на антресолях", "Палатка - на балконе" и т.д. Так вот сохраняете в файл...

По приезду на место читает - вынимаете из бумажника бумажки с надписями, где что лежит. Но хуже того, что шкаф теперь совсем другой, балкон тоже, так что втык от жены - это примерно и есть результат вот такого хранения и попытку раскрыть палатку, которой нет...

Примерный набросок, как бы писал-читал я. Набросок - надо дописать проверки и т.п. Функции могут быть переделаны в операторы вывода, но мне это не кажется лучшим способом... Да, касты к char* я тоже опустил для краткости, сами допишите. Сами функции тоже можно оптимизировать - например, выделять память прямо в строке и читать в нее...

void writeStr(const string& s, ostream& f)
{
    int l = s.length();
    f.write(&l,sizeof(int));
    f.write(s.data(),l);
}
void readStr(string& s, istream&f)
{
    int l;
    f.read(&l,sizeof(int));
    char * str = new char[l+1];
    f.read(str,l);
    str[l] = 0;
    s = str;
    delete[] str;
}

Потом запись в файл вашего класса выглядит как

void writeInf(const Legal&l,ostream&f)
{
    writeStr(l.name,f);
    writeStr(l.phone,f);
    ...
}

Ну, и чтение:

void readInf(Legal&l, istream&f)
{
    readStr(l.name,f);
    readStr(l.phone,f);
    ...
}

Примерно так...

Update

Вот полный пример кода:

#include <string>
#include <iostream>
#include <fstream>
using namespace std;
class Test
{
public:
    Test(const char * a = "",
         const char * b = "",
         const char * c = "")
        :a(a),b(b),c(c){}
    void write(ostream&f) const
    {
        writeStr(a,f);
        writeStr(b,f);
        writeStr(c,f);
    }
    void read(istream&f)
    {
        readStr(a,f);
        readStr(b,f);
        readStr(c,f);
    }

    friend ostream& operator << (ostream&f, const Test&t)
    {
        return f << "(" << t.a << "," << t.b << "," << t.c << ")";
    }
private:
    string a, b, c;
    static void writeStr(const string& s, ostream& f)
    {
        size_t l = s.length();
        f.write((const char*)&l,sizeof(size_t));
        f.write(s.data(),l);
    }
    static void readStr(string& s, istream&f)
    {
        size_t l;
        f.read((char*)&l,sizeof(size_t));
        char * str = new char[l+1];
        f.read(str,l);
        str[l] = 0;
        s = str;
        delete[] str;
    }
};
int main(int argc, const char * argv[])
{
    Test x("x","1","2"), y("y","3","4");
    Test u,v;
    cout << u << "\n" << v << "\n\n";
    {
        ofstream out("data",ios::binary);
        x.write(out);
        y.write(out);
    }
    {
        ifstream in("data",ios::binary);
        u.read(in);
        v.read(in);
    }
    cout << u << "\n" << v << "\n\n";
}
Answer 2

Вам нужно написать для своего класса функцию-член для сериализации объекта в массив байт и комплементарный выгрузке конструктор объекта из этого массива. Массив байт Вы и в файл выгрузите и в коммуникационный канал. Теперь о том, как делать сериализацию объекта Вашего (и любого) класса в массив.

1) Сложные структурные типы выгружаются в массив как последовательность отдельных членов. Если класс является производным или содержит указатели на объекты другого типа, для них пишутся отдельные сериализация и комплементарный конструктор.

2) Целые типы -- при выгрузке никаких int, long и прочего резинового использовать нельзя, только из stdint.h. Собственно, все что уходит за пределы программы, даже в пределах одного компьютера, должно описываться только типами из stdint.h и последовательностями из них.

3) std::строки вы выгружаете в массив в виде паскаль-строк, т.е. каждая строка байт предваряется целым, содержащим ее длину в байтах.

Потом, когда вы будете конструировать объект, вы эти числа можете использовать как смещения при работе с указателями. И да, без указателей Вы ничего не сериализуете.

По данному вопросу очень полезно почитать стандарт ASN.1 и стандарт на формат IIF.

READ ALSO
QML. Repeater. Как узнать ID каждого элемента Repeater?

QML. Repeater. Как узнать ID каждого элемента Repeater?

Определенное количество кнопок создается с помощью Repeater(ListModel)Как обратится к определенной кнопки и узнать ее параметры(X, Y)? main

184
Как посчитать количество файлов в папке С++

Как посчитать количество файлов в папке С++

Буквально в конце написания проекта столкнулся с такой проблемой, что не могу придумать как посчитать количество файлов в папке, чтобы создать...

200
JS Как подружить select+option и localStorage

JS Как подружить select+option и localStorage

Помогите разобраться

215