Minor improvements to QMap in Qt

I understand that many may say that if QMap does not suit you, do not use it, there are many other options for associative containers in other libraries. But in tribute to the Qt framework as a whole (what they offered to the world), we believe that QMap can be slightly tweaked to provide better functionality.

Solving an old problem with QMap. If you have only one QMap (map1) in your program, then you can easily work with it: add (insert), change (also insert), delete (remove) elements in the container without problems.

To be concrete, let's assume that we are working with QVariantMap container. If we now create a second instance of the QVariantMap container (map2) and make it nested in the first QVariantMap container, then we can no longer simply take and change the elements of the second QVariantMap container (map2). More precisely, we can only take the second QVariantMap container by key from the first container, then change it and then have to completely replace the second container in the tree.

The example below shows that the element with the key key_before And key_after2 appears in the tree, key key_after1 absent:

example on Qt4
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QpVariantMap map1;
    QpVariantMap map2;

    map2.insert("key_before", "ok");

    map1.insert("map2", map2);

    map2.insert("key_after1", "ok"); // так не правильно

    // так сработает и "key_after2" добавится в map2
    QVariant &vmap2 = map1["map2"];
    QVariantMap map_2 = vmap2.toMap();
    map_2.insert("key_after2", "ok");
    map1.insert("map2" , map_2 );


    qDebug()<< "---------------------------------";
    qDebug()<< "map1 "<< QtJson::serialize1( map1 );
    qDebug()<< "---------------------------------";

   return 0;
}
вывод на экран:
---------------------------------
map1  "{
    "map2": {
        "key_after2": "ok",
        "key_before": "ok"
    }
}"
---------------------------------

The key_after2 appears because we take a whole copy of the entire map2 from map1 (by key “map2”) then add key_after2 in this line map_2.insert(“key_after2”, “ok”); and rewrite the entire map2 in the tree.

Now if you have a third container (map3) nested inside the second, what happens? It looks like to change an element in the third container you'll have to overwrite the second container along with the third, and so on.

The catch here is that you can't use something like map1[“map2”][“map3”][“prop”]=”value133″; (as in the same js or php).

In practice, you can only do it like this (correct me if I'm wrong):

map1.value[“map2”].toMap().value(“map3”).toMap().value(“map3”).toMap().insert(“prop”,”value133″);

Both value() and toMap() return only copies of the container's elements. This is a big problem.

That is, the more nesting levels, the more data will have to be rewritten, which of course cannot be called good programming. And why does this happen, because the data in QVariantMap containers is stored as QVariantwhich on the one hand is very convenient, but on the other hand, to get access to the elements of the second container we have to use only one known standard option, this is the method toMap(). And here it turns out that toMap() can return just a copy QVariantMap. And what should we do with it now? Correctly change it and then all that remains is to completely rewrite the entire branch in the tree.

How to solve this problem?

For the record: first, you should run a debugger through the Qt source code, and then a lot will become obvious. For example, tree elements (keys and values) are created only on the heap, never on the stack. This is logical, since the reference counting method for an object is used later, so that it is deleted only when the reference count for it is zeroed. So, on the stack, an object is deleted immediately after leaving the scope (for example, from a function).
And all the keys of the tree are connected to each other according to the principle: the first one points to the next one, etc.

All this leads to simple conclusions that nothing should stop us from adding a new key-value to the tree.
All we need is to find the parent (a container of type QVariantMap) and do the standard insert(key,value) for it.

And here it turns out that there is only one method that will allow this to be achieved, and that is void *data() class QVariant (but at least it exists). The values ​​of QVariantMap, as we understand, are QVariant and therefore we can access the contents of QVariant only by its standard means. And the standard means for the QVariant:Map type is the toMap() method, which returns a copy of QVariantMap. The question of why it does this is left to the conscience of Qt developers for now and we will think – can we get a pointer to QVariantMap from void* data(). Well, that's it, that very pointer to an object in memory (heap).

The only thing left for us to do is to cast the type via interpretet_cast (apparently). This is of course not safe, or rather not safe at all, who guarantees that there is a QVariantMap in memory? The only thing that will help is to first get a copy of QVariant from the toMap() method and then check it via isValid() and canConvert(QVariant::Map).

But we have a small extension of the QMap template class (let's call it QpMap) in order to work directly with tree elements from QVariantMap and work with them directly in memory (in the heap), we submit it to your judgment and share the results. We are not completely sure that we are doing everything correctly, but in the first testing everything works without problems.

class QpMap
#ifndef QPMAP_H
#define QPMAP_H

#include <QMap>
#include <QVariantMap>
#include <QString>
#include "common/json/my_json.h"

template <class Key, class T>
class QpMap : public QMap<Key,T>
{
public:
    QpMap():QMap<Key,T>(){};
    

    // -----------------------------------------------------------------
    // addToMap добавляет в QVariantMap ключ/значение на любой уровень
    // вложенности дерева QVarinatMap
    // -----------------------------------------------------------------

    bool addtoMap( const QStringList &lst,
                   const QString & key,
                   const QVariant & val,
                   bool createKey_IfNotExist = true)
    {
        QpMap<QString,QVariant> *pMap  = this;

        foreach( QString name , lst)
        {

            QVariantMap::Iterator it1 = pMap->find(name);

            if( it1 != pMap->constEnd()) // именно mmm->constEn...
            {
                // ---------------------------------------------------
                // если ключ СУЩЕСТВУЕТ получим ссылку на него в куче
                // ---------------------------------------------------

                if( it1.key() != name)
                {
                    return false;
                }

                if( ! getPtr ( it1.value() , &pMap ) ) // переставляем указатель на следующий QVariantMap
                    return false;

            }
            else if ( createKey_IfNotExist )
            {
                // -------------------------------------------
                // если ключ ЕЩЕ НЕ СУЩЕСТВУЕТ создадим его
                // -------------------------------------------

                pMap->insert( name, QpVariantMap() );
                //pMap->insert( name, new QpVariantMap() ); // можно и так

                QVariantMap::Iterator it2 = pMap->find(name);

                if( it2 != constEnd() && it2.key() == name)
                {
                    if( ! getPtr ( it2.value() , &pMap ) ) // переставляем указатель на следующий QVariantMap
                        return false;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
        // на выходе устанавливаем ключ значение в контейнере дерева

        //*mmm->insert(key, val); // ИЗМЕНИЛИ/ДОБАВИЛИ НОВЫЙ ЭЛЕМЕНТ !!!
        pMap->insert(key, val); //  странно но работает и так ????

        return true;
    }

    bool getPtr( QVariant &var, QpMap<QString,QVariant> **pMap)
    {
        // ---------------------------------------------------
        // приведение типа
        // ---------------------------------------------------

        *pMap = reinterpret_cast<QpMap<QString,QVariant>*>( var.data());

         QVariant *vv = reinterpret_cast<QVariant*>( var.data() );

        if(! vv->isValid() || ! vv->canConvert( QVariant::Map))
            return false;


        // проверили это  QVariantMap
        return true;
    }
};

typedef QpMap<QString,QVariant> QpVariantMap ;

#endif // QPMAP_H
now we use QpMap addtoMap in the example
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QpVariantMap map1;
    QpVariantMap map2;

    map2.insert("key_before", "ok");

    map1.insert("map2", map2);

    // так сработает и "key_after2" добавится в map2
    QVariant &vmap2 = map1["map2"];
    QVariantMap map_2 = vmap2.toMap();
    map_2.insert("key_after2", "ok");
    map1.insert("map2" , map_2 );

    map1.addtoMap( QString("map2").split(","), "key_after1" , "ok" );  // ТЕПЕРЬ ТАК РАБОТАЕТ!
    map1.addtoMap( QString("m2,m3,m4").split(","), "key_after1" , "ok" );  // И ТАК РАБОТАЕТ!

    qDebug()<< "---------------------------------";
    qDebug()<< "map1 "<< QtJson::serialize1( map1 );
    qDebug()<< "---------------------------------";

   return 0;
}
вывод
---------------------------------
map1  "{
    "m2": {
        "m3": {
            "m4": {
                "key_after1": "ok"
            }
        }
    },
    "map2": {
        "key_after1": "ok",
        "key_after2": "ok",
        "key_before": "ok"
    }
}"
---------------------------------

Posted on GitHub A_little_development_of_QMap.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *