Как стать автором
Обновить

Комментарии 9

Соглашусь, обязательно сделаю.

Здорово! Полезно! Спасибо!

4lenodevka Попробую с вами подискутировать:
Вы описываете механизм конвертации в JSON объектов с примитивными типами данных, но как быть со вложенными объектами? В вашем случае, если я конечно же правильно понял идею, придется на каждый класс писать свою реализацию своего виртуального оператора, что меня смущает. Моя идея заключается в единоразовом описании полей в Q_PROPERTY и последующей работой с пространством имен.
Насчет Enum и хардкода вектора я полностью с вами согласен, насчет вектора вообще ужасное решение.

А вот насчет выделения памяти надо разбираться, хранимые указатели на целевой QObject занимают всего по 8 байт (на x64), объект QMetaProperty 32 байта, действительно расход большой, 40 байт на хранитель, но эта память освобождается сразу же после разрушения фабрики, которая предоставила в пользование объекты keepers. (т.е. суммарно при сериализации объекта с 5ю полями, на время сериализации будет выделено 200 байт, не кисло). Стоит ли сериализация такого расхода, пусть и кратковременного — решать вам, вообще серализация довольно затратный по памяти процесс.
Как я сказал в конце — приходится чем-то жертвовать, если описывать реализацию оператора сериализации в классе, вы приобретаете в гибкости, но теряете в простоте использования. Более того, вам понадобится такой же оператор и для XML и для бинарного формата(который, я надеюсь, скоро появится в QSerializer), в итоге тут уже попахивает вынесением такой функциональности в отдельный класс (меня привлекает концепция миниатюрных классов, описанная в «Clean Code» Боба Мартина, и, на мой взгляд, стремление к простоте — вовсе не грех).

Я учту ваши замечания, считаю такой опыт полезным, поэтому спасибо за ваше время.

По поводу списков согласен, моё решение тоже костыль, наверное, бОльший в вашем случае. Я не придумал, как (де)сериализовать списки как обычный Q_PROPERTY без проверок на тип внутри (де)сериализатора (то же касается вложенных сложных объектов), особенно если это список наследников QObject и их надо пробрасывать в qml именно как список. Хочется чего-то, что не потребует изменения кода сериализатора для каждого нового типа. Обычно всё выливается в вид QList<QObject*> с неприятными кастами на каждом шагу (это всё для проброса в qml) либо в QQmlListProperty, где много лишнего кода в реализации (или макросы, но большие проекты компилируются медленнее из большого кол-во кода в header-е).


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

Да, правильно поняли, у вас эту проблему решают кастомный кипер.


Скажите, а при необходимости использования get/set из c++ в реальном коде вы используете MEMBER в Q_PROPERTY или какие-то макросы, которорые делают Q_PROPERTY/signal/getter/setter/{private,protected} поле? Честно говоря, с MEMBER из вашей статьи познакомился, интересно.


Я у себя использую макросы вида PROPERTY(type, name) и PROPERTY_DEFAULT(type, name, default), которые разворачиваются во всё вышеописанное + type m_ ## property = default во втором случае (что тоже неприятно радует временем компиляции, когда в проекте тысячи таких PROPERTY, зато избавляет от ошибок).


Ещё интересно, как вы возвращаете из getter-ов (если они есть) вложенные наследники QObject: как указатель, что, по-моему, плохая идея, либо как копию/ссылку на объект. Во втором случае в Qt есть два подхода: возвращение копии с последующим вызовом setter-а и возвращение ссылки, которую можно дергать иерархически и на любом уровне менять объект. По моему актуальный вопрос при работе с qml.


По поводу использования памяти у вас всё хорошо подытожено в заключении, остаётся только сказать, что есть примитивные классы (например, какой-нибудь vector3d), которых много и скаждым можно работать в qml. Тогда оверхед QObject-a в 16 байт выигрывает перед 40 байтами с дополнительными выделениями памяти.


Кстати, оба подхода позволяют работать не только с json. Нашёл очень удобным для себя оператор QDebug operator<<(QDebug d, const Object &o) с проходом по полям, вдруг пригодится.


Остаётся только пожелать, чтобы в Qt6 завезли хорошую годную рефлексию на QObject-based property и их списки. А вашу библиотеку обязательно попробую.

Хранить по объекту на property попахивает оверхедом, достаточно итерироваться по property и писать сразу в json, keeper-ы не нужны. Так быстрее и память не занимает.


Заголовок спойлера
BaseClass::operator const QJsonObject() const
{
    QJsonObject result;

    // use BaseClass static offset, not virtual
    for(int i = staticMetaObject.propertyOffset(); i < metaObject()->propertyCount(); i++)
    {
        auto key = metaObject()->property(i).name();
        auto type = metaObject()->property(i).userType();
        auto value = metaObject()->property(i).read(this);

        if(value.isNull())
        {
//            qDebug() << .err..
            continue;
        }

        auto converted = value.convert(type);

        if(!converted)
        {
//            qDebug() << .err..
        }

        result[key] = QJsonValue::fromVariant(value);
    }

    return result;
}

Наследуясь от такого класса любой объект получает (виртуальный) оператор и можно писать QJsonObject json = myObject и регистрировать типы в массивах не надо. Остаётся только добавить отличие USER свойств и вложенных объектов. Хардкодить std::vector в регистрации тоже как-то некрасиво. Я себе сделал SerializableVector класс с похожей логикой, лучше ничего не придумал). QVariant(val), кстати, не работает с enum-ами (которые зарегистрированы в Q_ENUM/Q_ENUM_NS).

ответил вам выше, добавлю еще, что вместо передачи QMetaProperty в хранитель можно передавать ее индекс в списке propertyes. Так можно оставить по 12 байт на хранитель вместо 40, как вариант.

Можно посмотреть еще в сторону Q_GADGET, что б сериализуемые объекты не были такими тяжёлыми с багажом от QObject.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории