Pull to refresh

Comments 38

Теперь мы можем смело переписать наш макрос.


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

Что в данном случае подразумевается под «отладкой»? Пошаговая трассировка в отладчике — да, это может быть затруднительно, если макрос сплошь состоит из вызовов функций. А вот в плане отладки «во что это экспандится» макросы на много голов выше шаблонов: начиная от gcc -E и заканчивая отладкой макроэкспанда в gdb.
P. S. только слепые рабы Саттера и Александреску боятся макросов, нормальные разработчики используют все доступные средства для повышения выразительности и читаемости кода.
Просто там багов неявных можно наловить, начиная от классических примеров с забытыми скобками и использованном два раза параметром, заканчивая тем, что макросы уважают только запятые в круглых скобочках, но не в фигурных, угловых или квадратных:
#define SIMPLE_MACRO(text) text

SIMPLE_MACRO( vector<int> v(1, 2) ); // OK
SIMPLE_MACRO( vector<int> v{1, 2} ); // Error, 2 arguments
SIMPLE_MACRO( array<int, 10> ar ); // Error, 2 arguments
SIMPLE_MACRO( [x, y](){} ); // Error, 2 arguments
Слабенькие в плюсах макросы, жалко, что их не улучшают.
Их надо уметь использовать, эта проблема легко решается:
#define SIMPLE_MACRO(...) __VA_ARGS__
А вот я недавно сделал себе макросы с дефолтными аргументами, это было чуть сложнее ))
В таком тривиальном случае — да, но в случае, когда мы хотим с аргументами что-нибудь делать, не всё так просто. Особенно если такой случай заранее не продумать.
Эм… Чем это решение лучше по сравнению с ::boost::serialization?
Возможно тем, что не использует boost? хотя использует pugi, с другой стороны
И, насколько я понимаю, необходимо наследоваться от Serializable, что тоже не всегда хорошо.
Вся соль была в синтаксисе. Упростить его так, что бы пользователю ненужно было писать свои функции для сериализации в каждом классе.
Не могу удержаться и не перефразировать: «Вся соль была в сахаре. В синтаксическом сахаре.»
Есть восможность сериализировать и, самое главное, десериализировать обьект производного класса полиморфно по указателю на базовый?
По указателю сериализовывать вообще нельзя но если очень хочется )) то можно поиграть с классом SerializerEX и добавить туда поддержку указателей. А сериализовывать и десериализовывать можно, если есть нужная функция для сериализации \ десериализации. В исходниках есть пример с полем унаследованным от Serializable что позволяет его сериализовать.
Это больноватое ограничение. К примеру, у вас есть вектор указателей на класс Widget. Что на самом деле за каждым из них скрывается известно лишь шефу Аллаха. И вот, Вам нужно все виджеты сохранить, а потом загрузить. У boost'а это достигается с помощью уродливых препроцессорных манипуляций. Любое другое решение было бы интересным.
Да тут это вроде из коробки работает, главное, чтобы во всей иерархии был только один Serializable объект и Widget был его потомком. Ах да, и ещё при текущей реализации надо, чтобы не было членов с одинаковыми именами, иначе в map один такой член затрет другого.
Как я понял он имеет ввиду работу с указателями.
Ну в данном случае нужно просто определить функцию на подобии такой
template<class T>
void Serialize(const string& _key, vector<string, Widget*>* _widgets, pugi::xml_node& _node)
{
	auto lv_Node = _node.append_child(_key.c_str());
	int i = 0;
	for (auto lv_Widget : *_widgets)
	{
		lv_Widget->Serialize( lv_Node.append_child( inttostr(i++) ) );
	}
}

Ну и соответственно для десериализации нечто подобное.
Вот это нечто подобное как раз и есть самое интересное. Ведь нужно вызвать оператор new с типом класса-наследника, а не с базовым.
Это уже вопрос фабрик. В функции сериализации можно указать что-то вроде
lv_Node.append_attribute("type") = Widget->Type();

а в десериализации
_widgets[i] = CreateWidget( lv_Node.attribute("type").as_string() );

В общем это уже выходит за пределы сериализатора, я для этого и вынес функции Serialize и Deserialize наружу, чтобы можно было легко расширять функционал.
И под каждый объект создается целый набор функторов, хранящих указатель на this, и не являющихся не только copy-safe, но и move-safe, и это без удаленных соответствующих конструкторов и операторов. Я уже молчу про неинициализированные фиктивные члены класса…
«Простой способ отстрелить всю ногу в C++. А почему бы и нет?»
и это без удаленных соответствующих конструкторов и операторов.

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

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

Куда спрятаны? У вас там default.

В смысле неинициализированные?

Извиняюсь, невнимательно прочитал код, в первом варианте у вас void Add(...), а во втором уже char.
Куда спрятаны? У вас там default.

Я понял прошу прощения, нужно было их просто пустыми оставить, это я почему то упустил.
Имелось ввиду для использования конечно ))
Какое-то у вас чрезмерно усложненное решение, на мой взгляд, для каждого экземпляра класса при создании выполняется дополнительный runtime код и тратится уйма памяти. При этом, насколько я понимаю, никакие плюсы подобного подхода (например, возможность в runtime для конкретного объекта поменять список сериализуемых элементов) вам не нужны, вся нужная информацию у вас есть в compile-time и единственное преимущество по сравнению с каким-нибудь банальным решением типа:
class Test
{
public:
    int SomeInt = 666;
    float SomeFloat = 42.2; 
    string SomeString = "Hello My Little Pony";

    void Serialize() {
        ::Serialize("SomeInt", SomeInt);
        ::Serialize("SomeFloat", SomeFloat);
        ::Serialize("SomeString", SomeString);
    }
};

лишь красивый синтаксис и отсутствие повторов. Если я прав, то подумайте над каким-нибудь таким решением: для каждого класса храним статическую переменную-tuple, в которой лежат имена и указатели на члены данных (pointer-to-member), что-нибудь вида:
class Test
{
public:
    int SomeInt = 666;
    float SomeFloat = 42.2; 
    string SomeString = "Hello My Little Pony";
private:
    static constexpr std::tuple<...> serialization_info { &Test::SomeInt, "SomeInt",
                &Test::SomeFloat, "SomeFloat",
                &Test::SomeString, "SomeString" };
};

Ну и, соотвественно, функциям сериализации вы просто передаете указатель на объект и ссылку на данный tuple, они по нему итерируются и делают всё, что нужно. Думаю, поплясав с бубном, можно всё это красиво обернуть и даже от этой статической переменной избавиться. Если интересно, можете ещё поглядеть недавнюю статью, она тяжеловато написана, но там похожая конструкция используется.
для каждого экземпляра класса при создании выполняется дополнительный runtime код и тратится уйма памяти.

К сожалению да, не зря есть фраза что за удобство нужно платить.
лишь красивый синтаксис и отсутствие повторов.

Да в этом и был смысл ))
то подумайте над каким-нибудь таким решением: для каждого класса храним статическую переменную-tuple, в которой лежат имена и указатели на члены данных (pointer-to-member), что-нибудь вида:

Да можно попробовать сделать та, но это было бы не так красиво как мне кажется.
К сожалению да, не зря есть фраза что за удобство нужно платить.
Имхо, это не C++ way.
Согласен, да и цель была просто попробовать реализовать фичу с подобным синтаксисом, получилось, но сомневаюсь что буду это использовать когда-нибудь в реальном проекте.
Не работал с msgpack, cereal, но по первому взгляду них всё тоже самое cereal не особо отличается от бустовской версии, а msgpack выглядит страшновато. Для protobuf'a нужно вообще писать протофайл формата. И я не ставил целью заменить их, сам активно использую протобаф, хотелось просто реализовать идею. Для несложных проектов возможно буду использовать это решения, для сложных скорее всего останусь на проверенном временем протобафе. Но спасибо за полезные ссылки сохранил себе.
Прочел бегло.
Как я понимаю, к каждому объекту, который мы хотим сериализовать, мы должны добавить соответствующие методы?

А в плюсах есть статическая информация о классе, которую можно получить в runtime? Например, получить список public членов и пройтись по ним, с автоматической сериализацией, в зависимости от типа? У нас в команде в дельфях так и сделали — проходятся все published property и они сериализуются в специальный контейнер, выдающий строку на выходе. Можно ли сделать так же?
Нет, интроспекция (compile-time) есть только на уровне proposal.
Как я понимаю, к каждому объекту, который мы хотим сериализовать, мы должны добавить соответствующие методы?

В данном случае эти методы прописываются макросами, самому ручами ничего писать не надо.
А в плюсах есть статическая информация о классе, которую можно получить в runtime?

Нет, в С++ нет рефлекшена. Потому и приходится писать подобные штуки.
Дочитал до слова virtual, а дальше не смог.
Sign up to leave a comment.

Articles