Перейти к содержанию

Сериализация/Десериализация объеков в С++


Antonshka

Рекомендуемые сообщения

Привет, привет, любители логических задач!  У меня снова есть для вас кое-что странное.

 

В коде приложения, определен класс Sample. Также, в коде приложения определены три его объекта-экземпляра, А, B, и C.

Класс Sample имеет одно единственное поле PTR, - указатель на функцию. Это поле по умолчанию равно nullptr.

В коде приложения есть множество разных функций. Сигнатуры этих функций совпадают с типом PTR.

 

Запускаем приложение!

Наша единственная задача в приложении, - назначить для поля PTR объектов A, B, и C, свой указатель на какую-либо функцию. После назначения, мы закрываем приложение.

Приложение должно сохранить назначенные указатели на диск. При следующем запуске приложения, оно, приложение, найдя файл сохранения, прочитает его, и восстановит значения полей объектов А, B, и C.

 

Но это еще не все. Мы можем захотеть создать еще несколько объектов динамически, повторив с ними тоже, что было сделано с объектами А, B, и C. И теперь, приложение должно при закрытии сохранить еще и динамически созданные объекты, плюс и их поля.

 

Как вам такой сюжет?

 

 

Изменено пользователем Antonshka
Ссылка на комментарий
Поделиться на другие сайты

Подскажи, что за дилер тебе такую отборную дурь подгоняет??? 

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

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

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

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

Ссылка на комментарий
Поделиться на другие сайты

Я вернулся к написанию библиотеки GUI.

Казалось бы, самодельная система PopupMenu уже закончена, но нет, мне пришла странная идея сделать её лучше чем в QT, - больше функционала, больше гибкости. PopupMenu на любой вкус и цвет. Причем для этого абсолютно не придется менять что-то в классе, - меняй только открытые свойства.

 

И это идея удалась, - благодаря всем нам конечно. Система PopupPenu, а с ней и система MainMenu, получились ну реально крутые.

Дело дошло до того, что была организована интерактивная замена PopupMenu'ек и их итемов в режиме реального времени. Как например в FireFox, когда можно мышкой ухватить итем какой-либо закладки в PopupMenu, и перетащить в другой PopupMenu. Причем создается такое окно-отрывок (snippet),  с итемом на нем.

Конечный пользователь может сам создать новое PopuMenu, новый итем. Также пользователь может изменить Click-функцию для итема, например выбрав из выпадающего списка нужную опцию, нужную функцию для этого конкретного итема.

Говоря короче, конечный пользователь (здесь это тот кто запустит уже готовый exe) может сам настроить под себя всю систему MainMenu и PopupMenu, - если разработчик позволит ему так делать, например добавив чек-бокс, для активации режима модификации.

 

Для сохранения всех переделок, сделанных конечным пользователем, нужна сериализация/десериализация.

Я поторопился, - интерактивная система была создана раньше системы сериализации. Хотя, когда только все начиналось, какое-то представления, как эту сериализацию потом организовать, у меня все-таки было. Но сейчас, я не могу вспомнить, что я там напланировал.

 

В любом случае, рано или поздно, эта система сериализации будет создана.

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

А причем тут указатели? Ты будешь из десериализации поднимать  новые объекты, и их указатели прописывать в нужные места другим объектам?

Ссылка на комментарий
Поделиться на другие сайты

1 час назад, Xipho сказал:

А причем тут указатели?

Представь себе главное меню какого-либо приложения. Первый элемент такого меню обычно называется Файл. Нажимая на него, появляется PopupMenu. В этом PopupMenu обычно следующие названия пунктов, - Открыть, Сохранить, Сохранить как, Выход, и т.п.

Пункт Открыть вызывает к примеру функцию VOID Open(), - так предусмотрел разработчик. Однако, пользователь, по желанию разработчика, может активировать режим модификации. Он, пользователь, включив такой режим, открывает этот PopupMenu, где Открыть, Сохранить, Сохранить как, Выход, затем, кликает по нему правой кнопкой мыши, перед ним появляется другое PopupMenu, над этим PopupMenu. Это второе PopupMenu, имеет пункты со следующими названиями, - Изменить название пункта, Изменить опцию пункта (то есть функцию), и другие.

Выбрав Изменить опцию пункта, появляется окно со списком (ListView например, или TreeView). В нем перечислены все (дозволенные) существующие опции/функции, одну из которых можно назначить на этот пункт.

Например, есть функция VOID SaveAs() для пункта Сохранить как. Пользователь может выбрать ее из списка и назначить на пункт Выход. Это просто пример.

 

Так вот, текущая задача, - это сериализация/десериализация этого указателя на функцию VOID SaveAs(). Так чтобы она теперь была для пункта Выход. Даже после перезапуска приложения.

 

Частично это задача теперь уже решена, если даже не полностью -

Это поле класса PopupMenu.

inline static std::map<std::wstring, OnClickForItemOfPopupMenu> userClickFunctionsForItems;

Разработчик заполняет этот std::map, определяя тем самым для пользователя допустимые варианты выбора опций.

WGW::PopupMenu::userClickFunctionsForItems.insert(std::make_pair(L"Открыть", open)); //open, это указатель на VOID open()
WGW::PopupMenu::userClickFunctionsForItems.insert(std::make_pair(L"Параметры", showParameters)); //showParameters, это указатель на VOID showParameters()
WGW::PopupMenu::userClickFunctionsForItems.insert(std::make_pair(L"Копировать", copy)); //copy, это указатель на VOID copy()

Пользователь, в ListView контролле, выбирает либо Открыть, либо Параметры, либо Копировать. Выбрав например Параметры, это слово сохраняется в поле userItemClickFunction класса Item. Все, переназначение опции/функции для конкретно этого пункта совершено.

Новая функция вызовется через следующее выражение одного их методов класса PopupMenu, при клике по пункту.

userClickFunctionsForItems.at(item->originalItem->userItemClickFunction)(&onClickEventForItemOfPopupMenu);
item->originalItem->userItemClickFunction //userItemClickFunction это std::wstring равное Открыть, Параметры, или Копировать, смотря что выбрал пользователь.

 

Это дело с указателем сейчас работает нормально. Для сериализации/десериализации нужно по идее просто сохранить/восстановить это поле со строкой - userItemClickFunction класса Item.

 

Сейчас нужно придумать сохранение/восстановление объектов PopupMenu. Плюс их правильное назначение для контроллов, после восстановления.

Ссылка на комментарий
Поделиться на другие сайты

Появилась хорошая идея, как сохранить/восстановить объекты PopupMenu.

Раньше PopupMenu назначалось контроллу по указателю.

Спойлер
//Псевдокод.
class PopupMenu {};
class Button {
  private:
  PopupMenu* popupMenu{ nullptr };
};

int main() {
  PopupMenu pop1 = new PopupMenu();
  PopupMenu pop2 = new PopupMenu();
  PopupMenu pop3 = new PopupMenu();
  Button btn1 = new Button();
  Button btn2 = new Button();
  Button btn3 = new Button();
  
  btn1.popupMenu = pop1;
  btn2.popupMenu = pop1; //Можно один и тот же PopupMenu назначать разным контроллам.
  btn3.popupMenu = pop2;
  
  return 0;
}

 

 

Теперь будет так

Спойлер
//Псевдокод.
class Widget { //Базовый класс для всех контроллов, будь-то PopupMenu, Button, EditBox
  privat:
  	inline static std::vector<Widget*> allWidgetsInLib;
  private:
  	INT popupMenuKey{ -1 };
}

class PopupMenu : public Widget {
  public:
    PopupMenu() {
      	//Допустим в векторе allPopupMenusInLib есть 5 штук PopupMenu.
		//Первые три PopupMenu имеют ключи (keyInAllPopupMenusInLib), 1, 2, 3, а четвертый имеет ключ 7, а пятый 12 (это например потому что пользователь 		  //удалил PopupMenu с 4 по 6, и с 8 по 11).
      	//Тогда новый ключ, для новосозданного PopupMenu, будет равен 4
		allPopupMenusInLib.push_back()
        keyInAllPopupMenusInLib = calculateMyKey() //метод calculateMyKey, пройдя по вектору, и встретив первую непоследовательнось ключей, вернет 4
    }
  	~PopupMenu() {
      //Пройди по вектору allWidgetsInLib, и у всех контроллов, у которых ключ PopupMenu равен моему, установи значение этого ключа в -1. Это значит 	 	   //PopupMenu не назначен.
    }
  privat:
  	inline static INT getKey(PopupMenu* popupMenuArg) {
      //Если popupMenuArg существует в allPopupMenusInLib, верни его ключ, то есть keyInAllPopupMenusInLib. Если не существует, верни -1
    }
  privat:
	inline static std::vector<PopupMenu*> allPopupMenusInLib;
  privat:
  	INT keyInAllPopupMenusInLib{ -1 };
};

class Button : public Widget {
  public:
  	INT addPopupMenu(PopupMenu* popupMenuArg) {
      //Если popupMenuArg существует в allPopupMenusInLib {
      	  popupMenuKey = PopupMenu::getKey(popupMenuArg);
          this->popupMenuKey = popupMenuKey;
      }
      return this->popupMenuKey;
    }
};

int main() {
  PopupMenu pop1 = new PopupMenu();
  PopupMenu pop2 = new PopupMenu();
  PopupMenu pop3 = new PopupMenu();
  Button btn1 = new Button();
  Button btn2 = new Button();
  Button btn3 = new Button();
  
  btn1.addPopupMenu(pop1);
  btn2.addPopupMenu(pop1); //Можно один и тот же PopupMenu назначать разным контроллам.
  btn3.addPopupMenu(pop2);
  
  deserializeSettings();
  //По закрытии приложения, где-то в дугом месте вызовется serializeSettings();
  return 0;
}

VOID deserializeSettings() {
  //Если файл сохранения существует, то загрузи и примени его.
  //Сохранение/Восстановление объектов и их ссылок теперь заключается в:
  // - при сериализации, каждый виджет сохраняет на диск свое поле popupMenuKey. При десериализации, 
  //   каждый виджет загружает с диска свое поле popupMenuKey.
  // - при сериализации, каждый PopupMenu сохраняет на диск свои атрибуты (цвет, фонт, и т.д), а также keyInAllPopupMenusInLib. При десериализации,
  //   каждый PopupMenu, сперва проверяет, есть ли уже в allPopupMenusInLib PopupMenu с его ключем (keyInAllPopupMenusInLib), и если уже есть,
  //   тогда просто замени его атрибуты своими, а если еще нет, тогда создай новый PopupMenu, со своим ключем и со своими атрибутами.
}

 

 

Ссылка на комментарий
Поделиться на другие сайты

12 часов назад, Antonshka сказал:

Это второе PopupMenu, имеет пункты со следующими названиями, - Изменить название пункта, Изменить опцию пункта (то есть функцию), и другие.

ИМХО - для рядового пользователя это сложно будет, сомневаюсь, что он оценит это.
исхожу из того, что вот по этому скрипту пару человек мне написали в ЛС - "а нельзя ли было проще сделать?"

Спойлер

image.png.418f21623657eb5ec8f5dd3b1486cf69.pngimage.png.9c330380d210b0c5c636264cdb540ae3.png

 

Ссылка на комментарий
Поделиться на другие сайты

3 минуты назад, Garik66 сказал:

ИМХО - для рядового пользователя это сложно будет, сомневаюсь, что он оценит это.

Менять весь интерфейс запущенного приложения конечно захочет не каждый. Это на выбор, при активированном режиме.

Ссылка на комментарий
Поделиться на другие сайты

В 18.12.2022 в 17:54, Xipho сказал:

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

 

Что если поле класса std::wstring? Плюс этот странный sizeof.

Спойлер
class Sample {
public:
	std::wstring text;
	INT value1{ 0 };
};

Sample sample;
sample.text = L"1234fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
WGW::GUILogger::write(WGW::LoggerNotifyType::Info, L"Sample size = %d\n", sizeof(sample));

//12/21/22  17:30:13 - [INFO] Sample size = 48

 

Спойлер
class Sample {
public:
	std::wstring text;
	INT value1{ 0 };
	INT value2{ 0 };
};

Sample sample;
sample.text = L"1234fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
WGW::GUILogger::write(WGW::LoggerNotifyType::Info, L"Sample size = %d\n", sizeof(sample));

//12/21/22  17:32:41 - [INFO] Sample size = 48
//Все равно 48

 

Спойлер
class Sample {
public:
	std::wstring text;
	INT value1{ 0 };
	INT value2{ 0 };
	INT value3{ 0 };
};

Sample sample;
sample.text = L"1234fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
WGW::GUILogger::write(WGW::LoggerNotifyType::Info, L"Sample size = %d\n", sizeof(sample));

//12/21/22  17:33:35 - [INFO] Sample size = 56
//Теперь 56

 

 

Такое странное поведение sizeof основано на Data structure alignment.

 

Ссылка на комментарий
Поделиться на другие сайты

Что-то @Xipho не ответил. Возможно времени нет у него.

В любом случае, с сериализацией/десериализацией я закончил. Основа этого дела, это использование std::unordered_map. Ключ используется для выбора правильного объекта.

Строки сохраняются в файл на диск так, - сперва сохраняется длина строки, затем ее содержимое.

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

 

Теперь можно заняться улучшайзенгом кода. Превращением его из каши во что-то более понятное. А то @Xipho, увидев его, потеряет сознание.

Прошел год, (365 дней), а я написал только PopupMenu, MainMenu, ToolTip, и TitleBar (в форме кода-каши). Ай да молодец я.

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

В 24.12.2022 в 16:21, Antonshka сказал:

Что-то @Xipho не ответил.

Ммм, не думал, что надо на что-то предыдущее ответить. Ты вроде очевидные вещи там написал...

 

В 24.12.2022 в 16:21, Antonshka сказал:

В любом случае, с сериализацией/десериализацией я закончил. Основа этого дела, это использование std::unordered_map. Ключ используется для выбора правильного объекта.

Не понял твою идею, ну да ладно. Я бы на твоём месте не стал изобретать велосипеды, а использовал бы protobuf в бинарном варианте.

 

В 24.12.2022 в 16:21, Antonshka сказал:

Строки сохраняются в файл на диск так, - сперва сохраняется длина строки, затем ее содержимое.

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

 

В 24.12.2022 в 16:21, Antonshka сказал:

Буфер для строки нужно предварительно увеличить

Наверное, ты хотел сказать, что инициализировать буфер для строки нужно строго после вычитывания её длины, чтобы сразу выделить правильный объем памяти?

 

В 24.12.2022 в 16:21, Antonshka сказал:

Теперь можно заняться улучшайзенгом кода. Превращением его из каши во что-то более понятное.

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

 

В 24.12.2022 в 16:21, Antonshka сказал:

А то @Xipho, увидев его, потеряет сознание.

Меня не так-то просто напугать, я всякое видел ))

 

В 24.12.2022 в 16:21, Antonshka сказал:

Прошел год, (365 дней), а я написал только PopupMenu, MainMenu, ToolTip, и TitleBar (в форме кода-каши).

Потому что распылял своё внимание, не шёл последовательно, не намечал план (хотя бы в том же Trello).

Ссылка на комментарий
Поделиться на другие сайты

1 час назад, Xipho сказал:

Не понял твою идею, ну да ладно. Я бы на твоём месте не стал изобретать велосипеды, а использовал бы protobuf в бинарном варианте.

Идея с std::unordered_map простая. Все объекты PopupMenu хранятся в unordered_map. У каждого объекта PopupMenu есть специальное поле, которое есть его ключ в unordered_map. Этот ключ уникальный, его тип INT. Указатель же на сам объект хранится как значение в unordered_map. То есть, unordered_map(Key, Value) = unordered_map(INT, PopupMenu*)

При сохранении объектов из unordered_map в файл на диск, сохраняется также и это поле-ключ, у каждого объекта. При следующем запуске приложения, значение Value(PopupMenu*) уже будет другое, но ключ будет тот-же самый. Поэтому при считывании файла, нужно будет только сверить ключи.

Посмотрю про protobuf. Интересно, что это. Спасибо.

 

1 час назад, Xipho сказал:

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

Архитектура библиотеки со дня ее создания почти не изменилась. Она меня устраивает.

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

Конечно, было бы интересно посмотреть как пишут программы специалисты, поучиться у них.

 

1 час назад, Xipho сказал:

Потому что распылял своё внимание, не шёл последовательно, не намечал план (хотя бы в том же Trello).

Кое-какой план все же есть.

У меня есть блокнот, в котором я пишу задания. Например, - сделать нужно то-то и то-то, это нужно удалить, это добавить, над этим подумать.

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.