Сериализация класса, который содержит std :: string - PullRequest
13 голосов
/ 13 августа 2011

Я не эксперт по С ++, но пару раз в прошлом сериализировал вещи. К сожалению, на этот раз я пытаюсь сериализовать класс, который содержит std :: string, что, как я понимаю, очень похоже на сериализацию указателя.

Я могу записать класс в файл и снова прочитать его. Все поля int хороши, но поле std :: string выдает ошибку «адрес вне границ», предположительно потому, что оно указывает на данные, которых там больше нет.

Есть ли стандартный обходной путь для этого? Я не хочу возвращаться к массивам символов, но, по крайней мере, я знаю, что они работают в этой ситуации. При необходимости я могу предоставить код, но надеюсь, что хорошо объяснил свою проблему.

Я сериализую, приведя класс к типу char * и записав его в файл с помощью fstream. Чтение, конечно, только наоборот.

Ответы [ 7 ]

11 голосов
/ 13 августа 2011

Я сериализую, приведя класс к типу char * и записав его в файл с помощью fstream.Чтение, конечно, только наоборот.

К сожалению, это работает только до тех пор, пока не задействованы указатели.Возможно, вы захотите дать свои классы void MyClass::serialize(std::ostream) и void MyClass::deserialize(std::ifstream) и назвать их.В этом случае вам понадобится

std::ostream& MyClass::serialize(std::ostream &out) const {
    out << height;
    out << ',' //number seperator
    out << width;
    out << ',' //number seperator
    out << name.size(); //serialize size of string
    out << ',' //number seperator
    out << name; //serialize characters of string
    return out;
}
std::istream& MyClass::deserialize(std::istream &in) {
    if (in) {
        int len=0;
        char comma;
        in >> height;
        in >> comma; //read in the seperator
        in >> width;
        in >> comma; //read in the seperator
        in >> len;  //deserialize size of string
        in >> comma; //read in the seperator
        if (in && len) {
            std::vector<char> tmp(len);
            in.read(tmp.data() , len); //deserialize characters of string
            name.assign(tmp.data(), len);
        }
    }
    return in;
}

Возможно, вы также захотите перегрузить потоковые операторы для более удобного использования.

std::ostream &operator<<(std::ostream& out, const MyClass &obj)
{obj.serialize(out); return out;}
std::istream &operator>>(std::istream& in, MyClass &obj)
{obj.deserialize(in); return in;}
10 голосов
/ 13 августа 2011

Простая запись двоичного содержимого объекта в файл не только непереносима, но, как вы узнали, не работает для данных указателя.В основном у вас есть два варианта: либо вы пишете реальную библиотеку сериализации, которая правильно обрабатывает std :: strings, например, используя c_str () для вывода фактической строки в файл, либо вы используете превосходную повысить сериализацию библиотеки.Если это вообще возможно, я бы порекомендовал последнее, затем вы можете сериализовать с помощью простого кода, подобного следующему:

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/string.hpp>

class A {
    private:
        std::string s;
    public:
        template<class Archive>
        void serialize(Archive& ar, const unsigned int version)
        {
            ar & s;
        }
};

Здесь функция serialize работает для сериализации и десериализации данных, в зависимости от того, какВы называете это.См. Документацию для получения дополнительной информации.

2 голосов
/ 13 августа 2011

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

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

Альтернативой является использование разделителя и экранирования, но он требует больше кода и медленнее как при сериализации, так и при десериализации (однако результат можно сохранить для чтения человеком).

2 голосов
/ 13 августа 2011

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

Я бы сделал функцию-член, которая бы взяла fstream и считал бы данные из нее, а также обратную функцию, которая бы принималаfstream и запишите его содержимое для восстановления позже, например:

class MyClass {
pubic:
    MyClass() : str() { }

    void serialize(ostream& out) {
        out << str;
    }

    void restore(istream& in) {
        in >> str;
    }

    string& data() const { return str; }

private:
    string str;
};

MyClass c;
c.serialize(output);

// later
c.restore(input);

Вы также можете определить operator<< и operator>> для работы с istream и ostream для сериализациии восстановите свой класс, если хотите этот синтаксический сахар.

0 голосов
/ 13 августа 2011
/*!
 * reads binary data into the string.
 * @status : OK.
*/

class UReadBinaryString
{
    static std::string read(std::istream &is, uint32_t size)
    {
        std::string returnStr;
        if(size > 0)
        {
            CWrapPtr<char> buff(new char[size]);       // custom smart pointer
            is.read(reinterpret_cast<char*>(buff.m_obj), size);
            returnStr.assign(buff.m_obj, size);
        }

        return returnStr;
    }
};

class objHeader
{
public:
    std::string m_ID;

    // serialize
    std::ostream &operator << (std::ostream &os)
    {
        uint32_t size = (m_ID.length());
        os.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
        os.write(m_ID.c_str(), size);

        return os;
    }
    // de-serialize
    std::istream &operator >> (std::istream &is)
    {
        uint32_t size;
        is.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
        m_ID = UReadBinaryString::read(is, size);

        return is;
     }
};
0 голосов
/ 13 августа 2011

Я давно не кодировал C ++, но, возможно, вы могли бы сериализовать массив char.

Затем, когда вы откроете свой файл, ваш string будет просто указывать на массив.

Просто идея.

0 голосов
/ 13 августа 2011

Почему не просто что-то вроде:

std::ofstream ofs;
...

ofs << my_str;

, а затем:

std::ifstream ifs;
...

ifs >> my_str; 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...