Home

Constness of least Surprise

Donnerstag, 6. Januar 2011 | Autor:

Phy­sik­stu­dent Mar­tin K.1 hat mir wie­der einen net­ten klei­nen Bug zuge­tra­gen! Hier erst­mal der Codeausschnitt:

QList<qint32> countlist;
	//std::swap funktioniert nicht, daher manuelles tauschen
	int_swap = countlist.at(i);
	countlist.at(i) = countlist.at(j);
	countlist.at(j) = int_swap;
	string_swap = keylist.at(i);
	keylist.at(i) = keylist.at(j);
	keylist.at(j) = string_swap;

Und die Fehlermeldung:

wortliste.cpp: In function 'void quick_sort(QList<QString>, QList<int>)':
wortliste.cpp:49: error: assignment of read-only location 'countlist.QList<T>::at [with T = int](i)'
wortliste.cpp:50: error: assignment of read-only location 'countlist.QList<T>::at [with T = int](j)'
wortliste.cpp:52: error: passing 'const QString' as 'this' argument of 'QString& QString::operator=(const QString&)' discards qualifiers
wortliste.cpp:53: error: passing 'const QString' as 'this' argument of 'QString& QString::operator=(const QString&)' discards qualifiers

Viel­leicht mal ein paar Wor­te zum Lesen von Feh­ler­mel­dun­gen. Man fängt von oben an und sucht das ers­te Vor­kom­men von „error:”. Vor „error:” steht die Datei und die Zei­le, in der der Feh­ler ist, dahin­ter die Fehlermeldung.

wortliste.cpp:49: error: assignment of read-only location 'countlist.QList<T>::at [with T = int](i)'

Auf Deutsch:

wortliste.cpp, Zeile 49: Fehler: Zuweisung (assignment) an eine schreibgeschützte Stelle (read-only location) namens 'countlist.QList<T>::at [with T = int](i)'

Ein­fa­cher gesprochen:

Du hast versucht, einem Symbol (zB. Variable oder so) mittels = einen Wert zuzuweisen, obwohl das hier verboten ist.

Exkurs: Constness

War­um soll­te sowas ver­bo­ten sein? Na zum Bei­spiel, wenn das Sym­bol kei­ne Varia­ble ist, son­dern eine Kon­stan­te2:

1 = 2;

wäre das offen­sicht­li­che Bei­spiel. 1 ist nun­mal eine Kon­stan­te. Man kann nicht ein­fach sagen „ ‚1’ hat ab jetzt den Wert 2.” (und selbst wenn man es könn­te, wür­de man es wahr­schein­lich nicht wollen.)

Zwei­tes Beispiel:

const int e = 2.182818284590
// [...]
e = 0;

Wir wol­len, dass e in unse­rem Pro­gramm stets den Wert 2,182818284590 hat. Die Defi­ni­ti­on als Kon­stan­te mit­tels const garan­tiert uns, dass das auch tat­säch­lich so ist. Wenn wir nun ver­su­chen, e einen ande­ren Wert zuzu­wei­sen, bspw. durch die Zei­le e = 0;, erin­nert uns der Com­pi­ler „Nee, das geht nicht, du woll­test doch, dass e kon­stant ist!”

Das ist auch genau der Grund, wes­halb man const benutzt - man beauf­tragt qua­si den Com­pi­ler damit, sicher­zu­stel­len, dass bestimm­te Objek­te nicht ver­se­hent­lich ver­än­dert wer­den3. Die Eigen­schaft, konstant/const zu sein, nennt man Con­st­ness. Die kor­rek­te Ver­wen­dung von const nennt man Const-Cor­rect­ness.

Der Bug

Zurück zum Bug. Schau­en wir uns die vom Com­pi­ler genann­te Zei­le 49 an:

countlist.at(i) = countlist.at(j);

Wir haben oben fest­ge­stellt, dass die Feh­ler­mel­dung daher­rührt, dass die lin­ke Sei­te der Zuwei­sung read-only ist. War­um soll­te das Ergeb­nis von countlist.at(i) read-only sein? Was ist über­haupt das Ergeb­nis von countlist.at(i)? Wir suchen zuerst die Dekla­ra­ti­on von countlist raus..

QList<qint32> countlist;

Eine QList - offen­bar ein Daten­typ aus Qt4. Die Doku­men­ta­ti­on von QList bzw. QList::at kön­nen wir ein­fach ergoo­geln und fin­den5 die­se Sei­te: doc.trolltech.com/latest/qlist.html

Wie in sol­chen Refe­ren­zen üblich, steht ganz oben eine Lis­te der Mem­ber, ins­be­son­de­re der Funk­tio­nen der Klas­se - dort fin­den wir auch at():

const T & at ( int i ) const

Auch wenn man den Rest viel­leicht nicht ver­steht6, sieht man doch sofort, dass vor­ne const steht - die Funk­ti­on gibt also eine Kon­stan­te zurück7 - und einer Kon­stan­te kön­nen wir halt kei­nen neu­en Wert zuweisen.

Suchen wir also in der Doku­men­ta­ti­on nach einer ande­ren Funk­ti­on, die sich wie at ver­hält, aber den Wert nicht als const Wert zurück­gibt. Da es sich hier um eine Lis­te han­delt, liegt nahe, wonach man Aus­schau hal­ten muss..

T & operator[] ( int i )

Voi­là!8

Die ande­ren Feh­ler­mel­dun­gen im Sti­le von „discards qualifiers” sind im Grun­de das sel­be. Wahr­schein­lich ist die Mel­dung bes­ser zu ver­ste­hen, wenn sich statt
keylist.at(i) = keylist.at(j);
das äquivalente
keylist.at(i).operator=(keylist.at(j));
anschaut.
Der Feh­ler liegt dar­in, dass wir den Qua­li­fier, d.h. das Schlüs­sel­wort „const”, igno­riert (discard) haben, indem wir auf einem kon­stan­ten Objekt (das Ergeb­nis von keylist.at(i)) eine Funk­ti­on (operator=())auf­ge­ru­fen haben, die die­ses Objekt ändern könnte.

Wenn man dem Com­pi­ler sagen möch­te, dass eine Funk­ti­on ein Objekt nicht ändert, muss man an die Funk­ti­ons­de­kla­ra­ti­on hin­ten ein const hän­gen9. Das geht in die­sem Fall natür­lich nicht, weil = nun­mal eine Funk­ti­on ist, die das Objekt ändern muss. Außer­dem müss­ten wir die­se Ände­rung im Code von Qt vor­neh­men, was kei­ne gute Idee ist.

Aber egal - unse­ren Bug haben wir zumin­dest beseitigt!

Anm: Principle of least Surprise

Das Prin­zip der gerings­ten Über­ra­schung besagt nichts wei­ter, als dass man Sys­te­me so gestal­ten soll­te, dass der Benut­zer kei­ne Über­ra­schun­gen erlebt, denn dann kann er intui­tiv und flüs­sig mit dem neu­en Sys­tem arbeiten.

Qt beach­tet die­ses Prin­zip an die­ser Stel­le nicht. Die Funk­ti­on at() kennt man aus der Stan­dard­bi­blio­thek als Metho­de von bspw. vector und dort ist sie eben nicht const!10
Das macht es schwer, den Feh­ler zu sehen, weil man schlicht nicht damit rech­net, dass at() eine Kon­stan­te zurückgibt!

Des­we­gen nun als Schlusswort:
Beim Design immer an’s Prin­ci­ple of least Sur­pri­se denken 😉

  1. Name nicht im Gerings­ten geän­dert.
  2. = unver­än­der­bar => read-only
  3. ins­be­son­de­re auch dass man selbst sie nicht ver­se­hent­lich ver­än­dert
  4. Erkenn­bar am Q vor List
  5. Wenn man die Doku­men­ta­ti­on nicht fin­det, hängt man halt an den Suchstring noch „Docu­men­ta­ti­on” oder „Refe­rence” oder „API” an.
  6. const T & at ( int i ) const - Die Funk­ti­on gibt eine Refe­renz (&) auf eine Kon­stan­te (const) vom Typ T zurück. Der Name der Funk­ti­on ist at, sie nimmt einen Para­me­ter vom Typ int und die Funk­ti­on ver­än­dert die Lis­te nicht (das hin­te­re const)
  7. Der Wert muss ursprüng­lich nicht kon­stant gewe­sen sein, es reicht wenn die Funk­ti­on so dekla­riert ist, dass die Rück­ga­be const sein soll.
  8. Nicht funk­tio­nie­ren wür­de value(), weil value() kei­ne Refe­renz auf ein Ele­ment der Lis­te zurück­gibt, son­dern nur den Wert aus der Lis­te her­aus­ko­piert. Das ist nicht, was wir wol­len.
  9. Bei­spiel: void foo(int k) const; vgl. auch Fuß­no­te 5
  10. Genau­er: es ste­hen sowohl const als auch nicht-const-Vari­an­ten zur Ver­fü­gung.

Trackback: