PDA

View Full Version : Why const can disappear at QMap::value(...)



QtCrawler
17th December 2015, 08:03
Hello,

I have some basic questions, I'm very interested in.

If I have a QMap<int,MyObject*>* and call method value on it, the method returns a pointer to a const instance of MyObject.


QMap<int,MyObject*>* myMap = new QMap<int,MyObject*>();
myMap->insert(1,new MyObject());

MyObject const * object_one = myMap->value(1); // const instance of myObject. I'm not allowed to call non-const methods on it.
object_one->changeMe(); //compiler will complain about it


but I can write it like this:


MyObject * object_one = myMap->value(1); // non-const instance of myObject. I'm allowed to call non-const methods on it.
object_one->changeMe(); //compiler wont't complain about it


And now I can mutate the instance. I want to know if this works just because of c-compatibility, if it's ok to use it like that (I would say no)?

Further I want to know how should I pass such an instance to another object which should hold the instance as mutable member (MyObject* myobject):



OtherClass(MyObject * myobject) : myobject(myobject){ }

MyObject const * const_object_one = myMap->value(1); // const instance of myObject. I'm not allowed to call non-const methods on it.
MyObject * object_one = myMap->value(1); // non-const instance of myObject. I'm allowed to call non-const methods on it.

OtherClass* other_object = new OtherClass(const_object_one);//fails

OtherClass* other_object = new OtherClass(object_one);//works


Can I call the constructor passing the object via QMap::value, or should I use []->operator?

Thank you

yeye_olive
17th December 2015, 09:39
These are basic C++ questions, indeed.

MyObject const * object_one; declares object_one as a pointer to a const MyObject. You promise not to modify the MyObject instance through object_one.

Remember that an instance method has a hidden parameter: the "this" pointer to the receiver object. Just like any other pointer (or reference) parameter, you can specify whether it is const or not. To make it const, just qualify the method prototype itself with "const". A const method only has const access to *this. A non-const method has full access to *this, but can only be called for non-const objects. This is really the same as any other pointer or reference parameter.

The changeMe() method presumably is a non-const method that mutates *this, and the compiler refuses to call it for the const object *object_one.

So, if you want to mutate *object_one, just declare it as a non-const pointer.

Similarly, OtherClass::OtherClass(MyObject * myobject), as its signature implies, wants full access to *myobject, therefore you may not pass a const pointer as argument. If const access to *myobject is enough, you should change the signature to OtherClass::OtherClass(const MyObject * myobject).

QMap::value() and QMap::operator[] do two different things.

QMap::value() returns a copy of a value in the map. It is a const method because it does not need to mutate the QMap.

QMap::operator[] is overloaded. It has a const version (called for const QMaps) and a non-const version (called for non-const QMaps).
The const version does the same thing as QMap::value().
The non-const version finds the value for the given key. If it does not exist, it mutates the QMap by inserting a new value. In both cases, it returns a non-const reference to the value (as opposed to a copy), which you can then use to mutate the value.

QtCrawler
17th December 2015, 10:07
Ok tanks.

So I can use MyObject * object_one = myMap->value(1); and it is completely valid, although value() returns MyObject const * object_one (which is a copy of the value in the map).
Is this comparable to this?


QMap<QString,QString> stringMap;
stringMap.insert("key","value");

QString value1 = stringMap.value("key"); //same would be QString value1(stringMap.value("key"));


I don't unterstand following:

assigning a pointer to a const instance to a pointer to a non-const instance works here:


MyObject * object_one = myMap->value(1);

but it does not work in constructor, i.e. pass the const instance to a pointer to the constructor and assign it to the pointer to a non-const instance which is a member.


OtherClass(const MyString * str){
this->str = str;
}

d_stranz
17th December 2015, 23:50
but it does not work in constructor, i.e. pass the const instance to a pointer to the constructor and assign it to the pointer to a non-const instance which is a member.

That's as it should be. Passing a const pointer and trying to assign it to a non-const pointer member variable breaks the promise that you will not modify the instance the pointer refers to. Even if you don't actually call any non-const methods using your copy of the pointer, the compiler has no way of knowing that in the constructor so it doesn't allow you to make a non-const copy of the pointer.

If indeed you never do call any non-const methods using the copy of the pointer, then simply declare the member variable as a const pointer and assign it in the constructor.


assigning a pointer to a const instance to a pointer to a non-const instance works here:

Actually, it shouldn't work. There is no non-const counterpart to value() as there is for operator[]. If that code actually compiles, I don't know why.

In the case of your QMap<QString, QString> example, line 4 is actually creating a new QString instance using the copy constructor from the const QString instance returned by value(). It is a duplicate of the one in the map, and you can change it to whatever you wish without changing the value that is held in the map. And in fact, in line 2 where you insert the string literal into the map, this is making a copy, too, and the string literal will disappear once the insert() statement exits.

QtCrawler
18th December 2015, 07:09
thnq d_stranz! The explanation is very clear.

Actually, it shouldn't work. There is no non-const counterpart to value() as there is for operator[]. If that code actually compiles, I don't know why.
I'm very interested in, why this works, and indeed it works. I tried it with gcc under unix and mingw under windows. Both worked.

Is it right if I say: If I have a Qt-Container containing pointers to Objects and I want to iterate over them and call a non-const method of the instance during iteration, I should never use the the standard iterators (QMapIterator, QListIterator, QHashIterator)?
Should I use instead the mutable counterparts (QMutableListIterator, QMutableMapIterator, etc...)? Or is it better to use patterns like this:


foreach(MyObject* cObject,values){//values is a list of MyObject-instances QList<MyObject*> values;
cObject->setMember("member");
}

And what if I want iterate over the map and delete every instance. Which iteration should I use there? I know there is qDeleteAll, but suppose there's nothing like that.

ChrisW67
18th December 2015, 09:54
So I can use MyObject * object_one = myMap->value(1); and it is completely valid, although value() returns MyObject const * object_one (which is a copy of the value in the map).

In your example the QMap<Key,T> template works out like this


QMap<int, MyObject*> map;
// ^ ^
// | +-- T == "MyObject*"
// |
// +-- Key == "int"

The function QMap::value() returns a "const T", that is, "const MyObject*" (constant pointer-to-obj) not "MyObject const *" (pointer-to-const-obj) . In assigning that return value to a pointer variable you make a copy of it, which may or may not be const.

Have a look at the variations in here:


#include <QCoreApplication>
#include <QMap>

class MyObject {
public:
void constFunc() const { }
void nonConstFunct() { }
};

int main(int argc, char **argv) {
QCoreApplication app(argc, argv);

MyObject *a = new MyObject;
MyObject const *b = new MyObject;

QMap<int, MyObject *> map1;
map1.insert(0, a); // ok
// map1.insert(1, b); // error, cannot put pointer-to-const in map

MyObject *c = map1.value(0); // ok
MyObject const *d = map1.value(0); // ok
c->nonConstFunct(); // ok
// d->nonConstFunct(); // error, we promised d pointed to a const object

// You can have a map contain pointer-to-const-object
QMap<int, MyObject const *> map2;
map2.insert(0, a); // ok
map2.insert(1, b); // ok

// c = map2.value(0); // error, map cannot put "MyObject const *" into a non-const pointer var
d = map2.value(1); // ok

d->constFunc(); // ok, pointer-to-const and const function
// d->nonConstFunct(); // error, pointer-to-const and non-const function

delete a;
delete b;

return 0;
}

QtCrawler
18th December 2015, 10:04
Ok thanks for your reply!

I thought const MyObject* is the same as MyObject const *, so a pointer to a const MyObject. (I read it from right to left.)

Vikram.Saralaya
18th December 2015, 13:26
"const MyObject*" (constant pointer-to-obj) not "MyObject const *" (pointer-to-const-obj)

@ChrisW67 I don't think so..

If I am not wrong, "MyObject * const" is a constant pointer-to-obj. The two things you mentioned are both pointers to const-obj.

yeye_olive
18th December 2015, 14:04
@ChrisW67 I don't think so..

If I am not wrong, "MyObject * const" is a constant pointer-to-obj. The two things you mentioned are both pointers to const-obj.

Indeed. ChrisW67 textually substituted "T" with "MyObject*" is his otherwise perfectly exact explanation, but I am sure he meant a semantic substitution: "immutable T" -> "immutable pointer to (mutable) MyObject" aka "MyObject *const".

d_stranz
18th December 2015, 16:34
Even after 25+ years of C++ coding, this is one aspect that continues to confuse me. And then there are the versions with two const declarations "const MyObject const *" or whatever. Thank god for google.

ChrisW67
18th December 2015, 19:37
It is a strange person that does not have to think twice (i wrote the code to check myself). As yeye_olive points out, even my explanation is a little imprecise. To be clear, value() returns type MyObject* const and not const MyObject* (more clearly written MyObject const *) as a naive text substitution interpretation of templates might lead you to think.

In my defence, it was a long week ;)

QtCrawler
18th December 2015, 21:00
Thank you guys.
It's great to discuss such topics with you.
Thank god for QtC ;)

yeye_olive
19th December 2015, 09:32
Even after 25+ years of C++ coding, this is one aspect that continues to confuse me. And then there are the versions with two const declarations "const MyObject const *" or whatever. Thank god for google.
C/C++ is quite liberal with the placement of the "const" qualifier in a declarator. IMHO it is a shame that the ambiguous form "const T *" be so prevalent. I tend to consistently place "const" after the type it qualifies, like * and &, which eliminates the ambiguous forms. All I have to do is read the declaration from right to left:
T const: constant T
T const *: pointer to constant T
T * const: constant pointer to T
T const * const: constant pointer to constant T

If you ever are in doubt about a declaration, check out the excellent cdecl.org (http://cdecl.org/).