Сериализация класса, содержащего std::string
Я не эксперт на С++, но в прошлом я делал сериализацию несколько раз. К сожалению, на этот раз я пытаюсь сериализовать класс, содержащий std::string, который, как я понимаю, в значительной степени похож на сериализацию указателя.
Я могу записать класс в файл и снова прочитать его. Все поля int прекрасны, но поле std::string дает ошибку "адрес за пределами границ", по-видимому, потому, что указывает на данные, которые больше не существуют.
Существует ли стандартное обходное решение? Я не хочу возвращаться к массивам char, но, по крайней мере, я знаю, что они работают в этой ситуации. Я могу предоставить код, если это необходимо, но я надеюсь, что я хорошо объяснил свою проблему.
Я сериализую, отбрасывая класс в char * и записывая его в файл с файлом fstream. Чтение, конечно, только наоборот.
Ответы
Ответ 1
Я сериализую, выставив класс в 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;}
Ответ 2
Просто запись двоичного содержимого объекта в файл не только не переносима, но, как вы признали, не работает для данных указателя. В основном у вас есть два варианта: либо вы пишете настоящую библиотеку сериализации, которая правильно обрабатывает 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
работает для сериализации и десериализации данных в зависимости от того, как вы это называете. Дополнительную информацию см. В документации.
Ответ 3
Самый простой метод сериализации для строк или других блоков с переменным размером - это сериализовать сначала размер при сериализации целых чисел, а затем просто скопировать содержимое в выходной поток.
При чтении вы сначала читаете размер, затем выделяете строку, а затем заполняете ее, читая правильное количество байтов из потока.
Альтернативой является использование разделителя и экранирования, но требуется больше кода и медленнее как при сериализации, так и при десериализации (однако результат может быть сохранен для чтения человеком).
Ответ 4
Вам придется использовать более сложный метод сериализации, чем приведение класса к 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
для сериализации и восстановления вашего класса, если вы хотите использовать этот синтаксический сахар.
Ответ 5
Почему не просто что-то вроде:
std::ofstream ofs;
...
ofs << my_str;
а затем:
std::ifstream ifs;
...
ifs >> my_str;
Ответ 6
/*!
* 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;
}
};
Ответ 7
Я не кодировал С++ в течение длительного времени, но, возможно, вы могли бы сериализовать массив char
.
Затем, когда вы открываете файл, ваш string
будет просто указывать на массив.
Просто идея.