Russian Qt Forum

Qt => Общие вопросы => Тема начата: Igors от Февраль 11, 2019, 13:11



Название: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 11, 2019, 13:11
Добрый день

Пример: в QVariant хранится std::vector (ну конечно зарегистировался Q_DECLARE_METATYPE). Теперь я хочу добавлять/удалять/менять эл-ты этого вектора. Не нашел как получить указатель или ссылку на данные хранимые в QVariant. Выходит всякий раз нужно перезаливать весь вектор :'(

Это нормально или я плохо искал? Если нормально, то каковы мотивы такого ограничения?

Спасибо


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Авварон от Февраль 11, 2019, 13:53
Да, это нормально, в мире без исключений метод, возвращающий ссылку, не может вернуть ошибку, если запрошенный тип не совпадает с тем, что реально лежит в QVariant. Только покрашить приложение с std::terminate.
Ну и о5 же всегда можно положить шаред_птр на вектор=)


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 11, 2019, 15:38
Ну и о5 же всегда можно положить шаред_птр на вектор=)
Хорошо, пример: вот есть член какого-то класса который

а) шарится
б) может иметь содержимое разных типов

Как "давить эти 2 окурка одновременно" ?


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Авварон от Февраль 11, 2019, 15:54
шаред_птр на вариант? ссылка на вариант?


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 11, 2019, 19:55
QVariant имеет implicit shared реализацию, как в прочем QByteArray, QString, QList и т.п. а это значит, что хранить ссылки и указатели на внутренние данные - это бомба с замедленным действием. В любой момент внутреннее значение может стать другим после неявной операции detach. Такими ссылками нужно пользоваться ооочень аккуратно.


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 12, 2019, 10:53
QVariant имеет implicit shared реализацию, как в прочем QByteArray, QString, QList и т.п.
По-моему никакой имплисит шары сам QVariant не имеет, это 12 байт, 8 для хранения данных (напрямую или указатель) + тип по которому спрыгивает на базовый класс с виртуалами, Используется тот же паттерн что и в boost::any (и вероятно std::any), только базовый класс "один на тип", отсюда необходимость регистрировать.

а это значит, что хранить ссылки и указатели на внутренние данные - это бомба с замедленным действием. В любой момент внутреннее значение может стать другим после неявной операции detach. Такими ссылками нужно пользоваться ооочень аккуратно.
Ну если внутри сидит имплист контейнер и брать константный указатель на его данные - то да. Но для QVariant нет вообще никакого  "доступа к содержимому"

шаред_птр на вариант? ссылка на вариант?
По сути нужна очень простая вещь - шарить вариант. Хотел так
Код
C++ (Qt)
typedef QSharedPointer<QVariant> TSharedVar;
И заряжать вариант нужным контейнером. Но доступа к эл-там контейнера нет, поэтому не годится. Сделать наоборот почему-то не допер  :)
Код
C++ (Qt)
typedef QSharedPointer<MyVector> TSharedVec;
QVariant v;
 
// заряжаем вариант
v.setValue(TSharedVec());
...
// получаем ссылку на вектор данных
TSharedVec sh= v.value<TSharedVec>();
if (sh.isNull()) throw;
MyVector & vec = *sh.data();
 
// оедактируем вектор
vec.resize(10);
 
Все вполне хорошо - и вариант есть и шарится. Не хватает одной вещи - сделать копию данных (в общем виде, не влезая в разборки с конкретными типами). Напр в первом случае это легко
Код
C++ (Qt)
typedef QSharedPointer<QVariant> TSharedVar;
TSharedVar sh;
...
sh.reset(new QVariant(*(sh->data()));
И каким бы ни было содержимое варианта - он скопируется корректно. А во втором случае получим еще один шаред ссылающийся на те же данные. Ну ладно, переживу сввитчами.


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 12, 2019, 12:35
QVariant имеет implicit shared реализацию, как в прочем QByteArray, QString, QList и т.п.
По-моему никакой имплисит шары сам QVariant не имеет, это 12 байт, 8 для хранения данных (напрямую или указатель) + тип по которому спрыгивает на базовый класс с виртуалами, Используется тот же паттерн что и в boost::any (и вероятно std::any), только базовый класс "один на тип", отсюда необходимость регистрировать.
https://doc.qt.io/qt-5/implicit-sharing.html


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 12, 2019, 13:34
https://doc.qt.io/qt-5/implicit-sharing.html
Ну вот, докатились до "тыц ссылкой" :)  Возможно Вы имели ввиду что QVariant понимает что хранит тип с имплисит шарой, но об этом речь не идет (std::vector). Сам по себе QVariant НЕ шарится


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 12, 2019, 16:12
https://doc.qt.io/qt-5/implicit-sharing.html
Ну вот, докатились до "тыц ссылкой" :)  Возможно Вы имели ввиду что QVariant понимает что хранит тип с имплисит шарой, но об этом речь не идет (std::vector). Сам по себе QVariant НЕ шарится
QVariant шарится))), и об этом явно написано в документации "тыц ссылкой", где приведена таблица классов Qt, использующих implicit shared.
QVariant может хранить практически любой copyable тип данных, и совершенно не догадывается о том какой он, с имплисит шарой или без.
Технических особенностей, почему бы не предоставить доступ к внутреннему содержимому, как, например, для QByteArray, никаких нет. Для std::any имеется std::any_cast< MyType & >.
Из-за отсутствия таких методов, QVariant постоянно использует копирование внутренних данных (fromValue/value), что приводит не редко к существенным потерям производительности.


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ViTech от Февраль 12, 2019, 16:50
Ну вот, докатились до "тыц ссылкой" :)  Возможно Вы имели ввиду что QVariant понимает что хранит тип с имплисит шарой, но об этом речь не идет (std::vector). Сам по себе QVariant НЕ шарится
QVariant шарится))), и об этом явно написано в документации "тыц ссылкой", где приведена таблица классов Qt, использующих implicit shared.

Можно такой ссылкой (https://github.com/qt/qtbase/blob/fd88c152db0949e47613858a914a6ae4a825781d/src/corelib/kernel/qvariant.cpp#L2364) тыцнуть, и по коду выяснять, шарится там что-нибудь или нет :). Хотя может сегодня оно шарится, а завтра уже нет (реализация поменялась).

В исходниках можно ещё метод data() заметить, который для внутреннего пользования. Но если очень хочется, то можно и попользовать (на свой страх и риск).


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 12, 2019, 21:42
Можно такой ссылкой (https://github.com/qt/qtbase/blob/fd88c152db0949e47613858a914a6ae4a825781d/src/corelib/kernel/qvariant.cpp#L2364) тыцнуть, и по коду выяснять ...
В исходниках можно ещё метод data() заметить, который для внутреннего пользования. Но если очень хочется, то можно и попользовать (на свой страх и риск).

Всегда поражала такая куча кода))). Более 5 тыс. строк  :o Все можно сделать намного меньшими усилиями.


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Old от Февраль 12, 2019, 21:46
Всегда поражала такая куча кода))). Более 5 тыс. строк  :o Все можно сделать намного меньшими усилиями.
Легаси же. :)
Сейчас можно, но когда закладывался QVariant шаблоны были в зачаточном состоянии. :)


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 13, 2019, 08:04
Всегда поражала такая куча кода))). Более 5 тыс. строк  :o Все можно сделать намного меньшими усилиями.
Легаси же. :)
Сейчас можно, но когда закладывался QVariant шаблоны были в зачаточном состоянии. :)

Обычно да). Но в данном случае не так. Из-за ограничений QVariant, пришлось реализовать собственный аналог ещё в 2009.
Весь функционал Qt и даже больше (с сериализацией, имплисит шарой и преобразованием пользовательских типов друг в друга) уместился ~1000..1500 строк.
Сейчас как раз подумываю о рефакторинге на новый стандарт). Наверное еще меньше получится).


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ViTech от Февраль 13, 2019, 11:16
Всегда поражала такая куча кода))). Более 5 тыс. строк  :o Все можно сделать намного меньшими усилиями.

Там документации примерно половина, и многовато возни со встроенными и своими кутешными типами. Обилие нешаблонных методов toSomeType() тоже свой вклад вносит :).

Сейчас как раз подумываю о рефакторинге на новый стандарт). Наверное еще меньше получится).

Рефакторинга вида:
Код
C++ (Qt)
#include <variant>
// or
#include <any>
не будет достаточно? :)


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 13, 2019, 12:09
Рефакторинга вида:
Код
C++ (Qt)
#include <variant>
// or
#include <any>
не будет достаточно? :)

 ;D ;D ;D

Сам тип, конечно , достаточно будет заменить на std::any) (но только с С++17).
Но там еще обвязка по динамической работе с типами и регистрации произвольной функциональности (кастование и преобразование типов, фабричные методы, сериализация и определенная пользователем любая другая).


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 13, 2019, 12:23
QVariant шарится))), и об этом явно написано в документации "тыц ссылкой", где приведена таблица классов Qt, использующих implicit shared.
Не всему надо верить, на заборе тоже много чего написано  :) Откройте переменную типа QVariant в отладчике - никаких "своих" счетчиков ссылок она не имеет.

QVariant может хранить практически любой copyable тип данных, и совершенно не догадывается о том какой он, с имплисит шарой или без.
Очень даже "догадывается", см напр флажок is_shared или хотя бы фрагмент кода что выше привел ViTech. Когда хранимый тип имеет имплисит шару, содержимое не копируется. Причем это почему-то поддерживается самим QVariant'ом (неясно зачем?). Но если, как в стартовом посте, хранимый тип std::vector - то никакой шары нет, и бедняга вектор гоняется туда-сюда всякий раз.  

Технических особенностей, почему бы не предоставить доступ к внутреннему содержимому, как, например, для QByteArray, никаких нет. Для std::any имеется std::any_cast< MyType & >.
Из-за отсутствия таких методов, QVariant постоянно использует копирование внутренних данных (fromValue/value), что приводит не редко к существенным потерям производительности.
Это я уже осознал, предлагаю вернуться к теме: как лучше сделать "шареный вариант"?

Сам тип, конечно , достаточно будет заменить на std::any) (но только с С++17).
Но там еще обвязка по динамической работе с типами и регистрации произвольной функциональности (кастование и преобразование типов, фабричные методы, сериализация и определенная пользователем любая другая).
Согласен, QVariant выглядит гораздо солиднее чем легковесный std::any.


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 13, 2019, 13:49
Не всему надо верить, на заборе тоже много чего написано  :) Откройте переменную типа QVariant в отладчике - никаких "своих" счетчиков ссылок она не имеет.

Да ладно? ) А здесь?

Код
C++ (Qt)
   struct PrivateShared
   {
       inline PrivateShared(void *v) : ptr(v), ref(1) { }
       void *ptr;
       QAtomicInt ref;
   };
 

Не шарятся только данные влезающие по размеру в

Код
C++ (Qt)
       union Data
       {
           char c;
           uchar uc;
           short s;
           signed char sc;
           ushort us;
           int i;
           uint u;
           long l;
           ulong ul;
           bool b;
           double d;
           float f;
           qreal real;
           qlonglong ll;
           qulonglong ull;
           QObject *o;
           void *ptr;
           PrivateShared *shared;
       } data;
 

Остальные, в том числе и ::std::vector, попадают в секцию else, где d->is_shared = true

Код
C++ (Qt)
static void customConstruct(QVariant::Private *d, const void *copy)
{
   const QMetaType type(d->type);
   const uint size = type.sizeOf();
   if (!size) {
       qWarning("Trying to construct an instance of an invalid type, type id: %i", d->type);
       d->type = QVariant::Invalid;
       return;
   }
 
   // this logic should match with QVariantIntegrator::CanUseInternalSpace
   if (size <= sizeof(QVariant::Private::Data)
           && (type.flags() & (QMetaType::MovableType | QMetaType::IsEnumeration))) {
       type.construct(&d->data.ptr, copy);
       d->is_shared = false;
   } else {
       void *ptr = type.create(copy);
       d->is_shared = true;
       d->data.shared = new QVariant::PrivateShared(ptr);
   }
}
 


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 13, 2019, 14:38
Остальные, в том числе и ::std::vector, попадают в секцию else, где d->is_shared = true
Да, Вы правы
Код
C++ (Qt)
QVariant v;
...
QVariant v2 = v;  // тут копирования std::vector не будет
Интересная возможность, не знал. Но увы, это никак не решает проблемы с доступом к содержимому


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 13, 2019, 15:05
Для пользовательских типов можно сделать так (со всеми оговорками про имплисит шару)

Код
C++ (Qt)
#include <QVariant>
#include <vector>
 
template < typename _Type >
inline _Type & qUserVariantAccess ( QVariant & value )
{
   Q_ASSERT( qMetaTypeId< _Type >() == value.userType() );
   return *reinterpret_cast< _Type *>( value.data() );
}
 
template < typename _Type >
inline const _Type & qUserVariantAccess ( const QVariant & value )
{
   Q_ASSERT( qMetaTypeId< _Type >() == value.userType() );
   return *reinterpret_cast< _Type *>( value.constData() );
}
 
int main ( int, char ** )
{
   QVariant variant;
 
   variant.setValue( ::std::vector< int >() );
   qUserVariantAccess< ::std::vector< int > >( variant ).push_back( 3 );
   qUserVariantAccess< ::std::vector< int > >( variant ).push_back( 1 );
   qUserVariantAccess< ::std::vector< int > >( variant ).push_back( 2 );
 
   return 0;
}
 


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 13, 2019, 16:24
Для пользовательских типов можно сделать так (со всеми оговорками про имплисит шару)
Нелегальщина, но чертовски заманчиво, подумаю. Изменит все ссылающиеся QVariant что здесь и нужно. Хорошо, а как сделать копию данных (не зная конкретного типа)?


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ViTech от Февраль 13, 2019, 17:51
Код
C++ (Qt)
QVariant v;
...
QVariant v2 = v;
Интересная возможность, не знал. Но увы, это никак не решает проблемы с доступом к содержимому

Тут копирования не будет, но при попытке изменить данные, похоже, скопируется. В QVariant::data() (https://github.com/qt/qtbase/blob/fd88c152db0949e47613858a914a6ae4a825781d/src/corelib/kernel/qvariant.cpp#L4137) выполняется detach(), соответственно в qUserVariantAccess(мутабельном) тоже детачнется.

Код
C++ (Qt)
template < typename _Type >
inline const _Type & qUserVariantAccess ( const QVariant & value )
{
   Q_ASSERT( qMetaTypeId< _Type >() == value.userType() );
   return *reinterpret_cast< _Type *>( value.constData() );
}
И всё равно const'а не хватает ;).


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: ssoft от Февраль 13, 2019, 21:53
Код
C++ (Qt)
template < typename _Type >
inline _Type & qUserVariantAccess ( QVariant & value )
{
   Q_ASSERT( qMetaTypeId< _Type >() == value.userType() );
   return *reinterpret_cast< _Type * >( value.data() );
}
 
template < typename _Type >
inline const _Type & qUserVariantAccess ( const QVariant & value )
{
   Q_ASSERT( qMetaTypeId< _Type >() == value.userType() );
   return *reinterpret_cast< const _Type * >( value.constData() );
}
 
template < typename _Type >
inline _Type & qUserVariantSharedAccess ( QVariant & value )
{
   Q_ASSERT( qMetaTypeId< _Type >() == value.userType() );
   return *const_cast< _Type * >( reinterpret_cast< const _Type * >( value.constData() ) );
}
 

Последний осуществляет доступ без detach.

Нелегальщина, но чертовски заманчиво, подумаю. Изменит все ссылающиеся QVariant что здесь и нужно. Хорошо, а как сделать копию данных (не зная конкретного типа)?

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

Код
C++ (Qt)
QVariant v1;
...
QVariant v2 = v1; // shared - v2 идентичен v1
v2.data(); // detached copy - v2 и v1 разные копии
 


Название: Re: Указатель/ссылка на содержимое QVariant
Отправлено: Igors от Февраль 14, 2019, 04:54
Каких-то хаков здесь нет, так что вполне легально.
Ну все-таки методы data и constData не документированы
А копия создается автоматически при вызове любого неконстантного метода.
Да, все оказалось очень просто, спасибо