О чем тут речь. В контейнерах QMap (или также QHash) можно создавать многоуровневую структуру со всеми возможными типа данных (из набора QVariant и не только).
Но есть старая гребаная загвоздка - если вы допустим в QVariantMap добавили через метод insert новый элемент, то insert создает копию этого элемента и потом вам в дереве надо как-то найти этот элемент, если вы вдруг захотите его изменить (добавить, удалить чего-нибудь). И самое главное,что оригинальный QVariantMap вам уже никак не поможет, потому что с него сделана копия.
То есть это приводит к тому, что если вы этот элемент в оригинале потом изменили, то его копия ничего об этом не узнает.
Вторая проблема поважнее в том, что когда вы обращаетесь к элементу контейнера в дереве по ключу, контейнер возвращает вам копию объекта. И опять вы не можете изменить значение по ключу никак.
В итоге вы создаете как правило красивую древовидную структуру для хранения данных, но все данные вам надо добавлять в самом начале и далее ничего не заменить. Точнее заменять надо будет целиком всю ветку дерева, а это не по фен-шую.
Что имеем в начале
Вот простой пример:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
mmA.insert("mmB" , mmB);
mm.insert("mmA" , mmA);
mmB.insert("bbb1",111111);
mmA.insert("aaa1",222222);
QVariant & vmmA = mm["mmA"]; //это ссылка ок
QVariantMap &vmmA1 = vmmA.toMap(); // это уже копия
QVariantMap &vmmA2( mmA );
vmmA1.insert("test1" , "ok");
vmmA2.insert("test2" , "ok");
qDebug()<< "---------------------------------";
qDebug()<< "mm"<< QtJson::serialize1( mm );
qDebug()<< "---------------------------------";
return 0;
return a.exec();
}
Вывод на экран:
---------------------------------
mm "{
"mmA": {
"mmB": {
}
}
}"
---------------------------------
Элементов bbb1 и aaa1 нет в контейнере, потому что они добавлялись после добавления самого контейнера (которому они принадлежат). И это очень не удобно. Нужен способ добавлять в дерево mm элементы в любой время.
Что мы примерно хотим
Еще как пример у вас не такого штатного способа установить новое значение произвольному элементу:
mm["mmA"]["mmB"]["bbb2"]="234";
вам приходится делать так:
QVariant &rmmA = mm["mmA"];
QVariantMap &rmmA_ = rmmA.toMap() // а тут уже опять копия
Долгое время такого способа мы не знали, хотя внутренне подозрения были, что как-то можно выкрутится. В php и js это кстати штатно все работает без проблем.
Логика подсказывает, что все элементы в результате insert создаются в куче (через new или Malloc). Удаляется любой элемент контейнера после уменьшения всех количества всех ссылок на него до нуля (QBasicAtomicInt ref;). Так почему бы не добавить еще один элемент в контейнер.
И вот наконец решили мы разобраться в этой проблеме и выяснить как можно получить доступ к глубоко вложенному в дереве объекту и изменить его.
Просто полазили по коду Qt и выяснили,что штатных возможностей нет, точнее методы есть, но они все приватные, и приходится нам наследоваться от шаблонного класса QMap.
data()
Нашли мы только один реально нужный public метод это - data(), который всегда вернет указатель на объект (в куче) , который и является нашим объектом (значением ключа). Но указатель этот void*, а это значит, что кому-то надо проверять на валидность, что там в памяти сидит (там может быть все, что угодно). Примерно так можно его использовать:
QVariantMap::Iterator it = mm.find("mmA");
if( it != mm.constEnd() && it.key() == "mmA")
{
// нашли
QVariantMap *mmm = (QVariantMap*)(it.value().data());
*mmm->insert("test2" , "ok");
}
И это сработает! Но есть нюанс вам надо точно знать, что в it.value().data() у вас QVariantMap*, в противном случае вас ждет коллапс.
---------------------------------
mm "{
"int": 123,
"mmA": {
"mmB": {
},
"test2": "ok" // вот он появился
}
}"
---------------------------------
Теперь как изменить глубоко вложенный элемент
Тут надо пройтись по всем, начиная с корня контейнера, ключам и проделать примерно то же самое.
Вот такой наш шаблонный класс QpMap унаследованный от QMap подойдет:
#ifndef QPMAP_H
#define QPMAP_H
#include <QMap>
#include <QVariantMap>
#include <QString>
typedef QVariantMap QpVariantMap ;
template <class Key, class T>
class QpMap : public QMap<Key,T>
{
public:
QpMap():QMap<Key,T>(){};
inline bool addtoMap2( const QStringList &lst, const QString & key, const QVariant & val)
{
QVariantMap *mmm = this;
foreach( QString name , lst)
{
QVariantMap::Iterator it = mmm->find(name);
if( it != constEnd() && it.key() == name)
{
// нашли существующий по ключу элемент
mmm = static_cast<QVariantMap*>(it.value().data());
QVariant *vv = static_cast<QVariant*>(it.value().data());
if(! vv->isValid() || ! vv->canConvert( QVariant::Map))
return false;
// проверили это QVariantMap
}
else
return false;
}
*mmm->insert(key, val); // ИЗМЕНИЛИ/ДОБАВИЛИ НОВЫЙ ЭЛЕМЕНТ !!!
return true;
}
};
#endif // QPMAP_H
Используем его так:
mm.addtoMap2( QString("mmA,mmB").split(","), "test2" , "ok" );
Вывод - все работает похоже ОК:
---------------------------------
mm "{
"int": 123,
"mmA": {
"mmB": {
"test2": "ok"
}
}
}"
---------------------------------
Это первый наш опыт создания шаблонных классов, возможно есть еще нюансы (надо понаблюдать).
Развитие_QMap_доступ_и_изменение_элементов_дерева.mp4