Comments 18
Использование wide-литералов и std::wstring — так себе идея.
Взамен специализации шаблона функции в данном случае лучше прибегнуть к перегрузке и не передавать builtin типы по ссылке.
Явный вызов new/delete рядом с лямбда-выражениями и прочими C++11 фишками выглядит дико :-)
Взамен специализации шаблона функции в данном случае лучше прибегнуть к перегрузке и не передавать builtin типы по ссылке.
Явный вызов new/delete рядом с лямбда-выражениями и прочими C++11 фишками выглядит дико :-)
Использование wide-литералов и std::wstring — так себе идея.
не передавать builtin типы по ссылке
Хотелось бы узнать поподробнее, чем плохи wide-литералы, и как Вы бы предпочли сериализовать/десериализовать встроенные типы?
Явный вызов new/delete рядом с лямбда-выражениями и прочими C++11 фишками выглядит дико :-)
А как Вам вызов malloc в PrototypeFactory?
Про wchar_t есть неплохой ответ по ссылке stackoverflow.com/questions/11107608/whats-wrong-with-c-wchar-t-and-wstrings-what-are-some-alternatives-to-wide
В данном случае они как минимум являются преждевременной пессимизацией, так как зачастую sizeof(wchar_t) > sizeof(char), а все используемые в идентификаторах C++ символы помещаются в обычный char.
Про встроенные типы, я предлагаю заменить первое объявление на второе:
template <> void serialize (int& value, const wstring& name)
void serialize(const int value, const wstring& name)
Применение malloc/realloc оправдано, там где это нужно (свои аллокаторы, еще какие-то нужды). В данном коде необходимость malloc'a мне не ясна. Более того, судя по всему вы выделяете память под произвольный тип T и «инициализируете» его неким прототипом с помощью memcpy, а что если тип T не является trivially-copyable (например он строка, или любой другой тип, владеющий какими-то динамическими ресурсами)? Или десериализация нетривиальных типов не предполагалась?
Кстати, вот тут exception-unsafety имеется:
std::vector<BaseClass*> vec_ptr = { new BaseClass, new BaseClass, new MyConcreteClass, new SomeonesConcreteClass };
Любое исключение приведет к утечке памяти.
В данном случае они как минимум являются преждевременной пессимизацией, так как зачастую sizeof(wchar_t) > sizeof(char), а все используемые в идентификаторах C++ символы помещаются в обычный char.
Про встроенные типы, я предлагаю заменить первое объявление на второе:
template <> void serialize (int& value, const wstring& name)
void serialize(const int value, const wstring& name)
Применение malloc/realloc оправдано, там где это нужно (свои аллокаторы, еще какие-то нужды). В данном коде необходимость malloc'a мне не ясна. Более того, судя по всему вы выделяете память под произвольный тип T и «инициализируете» его неким прототипом с помощью memcpy, а что если тип T не является trivially-copyable (например он строка, или любой другой тип, владеющий какими-то динамическими ресурсами)? Или десериализация нетривиальных типов не предполагалась?
Кстати, вот тут exception-unsafety имеется:
std::vector<BaseClass*> vec_ptr = { new BaseClass, new BaseClass, new MyConcreteClass, new SomeonesConcreteClass };
Любое исключение приведет к утечке памяти.
Применение malloc/realloc оправдано, там где это нужно (свои аллокаторы, еще какие-то нужды). В данном коде необходимость malloc'a мне не ясна. Более того, судя по всему вы выделяете память под произвольный тип T и «инициализируете» его неким прототипом с помощью memcpy, а что если тип T не является trivially-copyable (например он строка, или любой другой тип, владеющий какими-то динамическими ресурсами)? Или десериализация нетривиальных типов не предполагалась?
При десериализации объекта мы, зная имя класса этого объекта, должны будем получить участок памяти равный размеру объекта этого класса. В PrototypeFactory мы его и получим. Естественно это будет точная копия объекта, созданного конструктором по умолчанию. Остальную работу по наполнению его содержимым должен будет проделать объект, получивший этот участок памяти.
Где этот код? Что будет с ним, если memcpy скопирует объект, владеющий какими-либо ресурсами?
void serialize (iSerializable*& object, const wstring& name)
{
if (!object) m_factory->get_object (object, m_node.find_child_by_attribute (L«name», name.c_str ()).name ());
object->serialize (get_node_by_attr (name));
}
— собственно пример использования из статьи.
Что касается ресурсов, то если конструктор класса по умолчанию создает объекты в динамической памяти, то это поле должно быть перезадано в полученном таким образом объекте.
Предполагается, что если мы используем для создания объектов класса PrototypeFactory, то мы должны обеспечить ему простой конструктор, не создающий ресурсов, и все данные будем вводить в объект после получения его из PrototypeFactory. Естественно в этом случае выделять память в конструкторе по умолчанию бессмысленно, так как она будет лежать в прототипе безо всякой пользы.
{
if (!object) m_factory->get_object (object, m_node.find_child_by_attribute (L«name», name.c_str ()).name ());
object->serialize (get_node_by_attr (name));
}
— собственно пример использования из статьи.
Что касается ресурсов, то если конструктор класса по умолчанию создает объекты в динамической памяти, то это поле должно быть перезадано в полученном таким образом объекте.
Предполагается, что если мы используем для создания объектов класса PrototypeFactory, то мы должны обеспечить ему простой конструктор, не создающий ресурсов, и все данные будем вводить в объект после получения его из PrototypeFactory. Естественно в этом случае выделять память в конструкторе по умолчанию бессмысленно, так как она будет лежать в прототипе безо всякой пользы.
Про встроенные типы, я предлагаю заменить первое объявление на второе:
template <> void serialize (int& value, const wstring& name)
void serialize(const int value, const wstring& name)
Но в таком случае мы не сможем изменить значение данных класса при десериализации.
Почему бы просто не взять буст?
(понятно, что оформлено все на скорую руку, но я примера ради)
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>
class MyConcreteClass
{
public:
MyConcreteClass()
{
}
MyConcreteClass (float a, double b)
: mFloat(a)
, mDouble(b)
{
}
float getFloat() const
{
return mFloat;
}
double getDouble() const
{
return mDouble;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & mFloat;
ar & mDouble;
}
float mFloat;
double mDouble;
};
template<typename T>
std::string serialize(T* object)
{
std::stringstream sss;
boost::archive::text_oarchive oa(sss);
oa << object;
return sss.str();
}
template<typename T>
T* deserialize(const std::string& object)
{
std::stringstream sss(object);
boost::archive::text_iarchive ia(sss);
T* retval;
ia >> retval;
return retval;
}
int main()
{
MyConcreteClass* mcc = new MyConcreteClass( 55.4f, 23.9 );
auto str = serialize(mcc);
std::cout << str << std::endl;
MyConcreteClass* dmcc = deserialize<MyConcreteClass>(str);
std::cout << "float: " << dmcc->getFloat() << "; double: " << dmcc->getDouble() << std::endl;
}
(понятно, что оформлено все на скорую руку, но я примера ради)
Написал этот код я просто из спортивного интереса, а статью решил написать после прочтения этой ветки обсуждения статьи, также посвященной сериализации, решив, что кому-нибудь это покажется любопытным.
Самое интересное, зачем здесь полиморфизм? Зачем сувать vtable туда, где этого не требуется? Всё же это C++, а не Java или C#, принципы разработки отличаются. Да и уж хотя бы использовали бы CRTP, тогда код будет более эффективным.
Уже достаточно давно заинтересовался темой сериализации, а если конкретно, то сериализацией объектов, хранящихся по указателю на базовый класс.Тут объекты полиморфны сами по себе по условию задачи.
Всё же это C++, а не Java или C#
Если Вы помните, C++ предоставляет множество парадигм и абсолютно не ограничивает их использование и даже смешивание. Конечно есть некий оверхед в использовании динамического полиморфизма. Но чем конкретно Вам не нравится подобный подход?
Cereal не смотрели?
Не так сложна сериализация, как страшна десериализация.
(с) Джейсон Стетхем
Очень непонятно в использовании.
На мой взгляд десериализатор должен выплевывать конкретный дочерний объект по указателю на базовый. При этом при добавлении нового конкретного типа никаких лишних телодвижений в базовых компонентах производиться не должно. Не должно быть никаких ограничений в архитектуре и составе конкретных типов, кроме необходимости реализовать то-то, то-то и сё-то (компилятор должен подсказать выводимыми ошибками).
Но по-моему, это из области фантастики и вообще C++ - боль.
Sign up to leave a comment.
Сериализация C++ с полиморфизмом и прототипами