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 Worte zum Lesen von Feh­ler­mel­dun­gen. Man fängt von oben an und sucht das erste Vor­kom­men von „error:”. Vor „error:” steht die Datei und die Zeile, 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: Con­st­ness

Warum sollte sowas ver­bo­ten sein? Na zum Bei­spiel, wenn das Sym­bol keine Varia­ble ist, son­dern eine Kon­stante2:

1 = 2;

wäre das offen­sicht­li­che Bei­spiel. 1 ist nun­mal eine Kon­stante. Man kann nicht ein­fach sagen „‚1’ hat ab jetzt den Wert 2.” (und selbst wenn man es könnte, würde 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­tion als Kon­stante 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 Zeile 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 quasi den Com­pi­ler damit, sicher­zu­stel­len, dass bestimmte Objekte nicht ver­se­hent­lich ver­än­dert wer­den3. Die Eigen­schaft, konstant/const zu sein, nennt man Con­st­ness. Die kor­rekte Ver­wen­dung von const nennt man Const-Correctness.

Der Bug

Zurück zum Bug. Schauen wir uns die vom Com­pi­ler genannte Zeile 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 linke Seite der Zuwei­sung read-only ist. Warum sollte 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­tion von countlist raus..

QList<qint32> countlist;

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

Wie in sol­chen Refe­ren­zen üblich, steht ganz oben eine Liste der Mem­ber, ins­be­son­dere der Funk­tio­nen der Klasse - 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 vorne const steht - die Funk­tion gibt also eine Kon­stante zurück7 - und einer Kon­stante kön­nen wir halt kei­nen neuen Wert zuweisen.

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

T & operator[] ( int i )

Voilà!8

Die ande­ren Feh­ler­mel­dun­gen im Stile von „discards qualifiers” sind im Grunde das selbe. Wahr­schein­lich ist die Mel­dung bes­ser zu ver­ste­hen, wenn sich statt
keylist.at(i) = keylist.at(j);
das äqui­va­lente
keylist.at(i).operator=(keylist.at(j));
anschaut.
Der Feh­ler liegt darin, 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­tion (operator=())auf­ge­ru­fen haben, die die­ses Objekt ändern könnte.

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

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

Anm: Prin­ciple of least Surprise

Das Prin­zip der gerings­ten Über­ra­schung besagt nichts wei­ter, als dass man Sys­teme so gestal­ten sollte, dass der Benut­zer keine Über­ra­schun­gen erlebt, denn dann kann er intui­tiv und flüs­sig mit dem neuen Sys­tem arbeiten.

Qt beach­tet die­ses Prin­zip an die­ser Stelle nicht. Die Funk­tion at() kennt man aus der Stan­dard­bi­blio­thek als Methode 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­stante zurückgibt!

Des­we­gen nun als Schluss­wort:
Beim Design immer an’s Prin­ciple of least Sur­prise denken ;)

  1. Name nicht im Gerings­ten geän­dert.
  2. = unver­än­der­bar => read-only
  3. ins­be­son­dere 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­tion nicht fin­det, hängt man halt an den Such­string noch „Docu­men­ta­tion” oder „Refe­rence” oder „API” an.
  6. const T & at ( int i ) const - Die Funk­tion gibt eine Refe­renz (&) auf eine Kon­stante (const) vom Typ T zurück. Der Name der Funk­tion ist at, sie nimmt einen Para­me­ter vom Typ int und die Funk­tion ver­än­dert die Liste nicht (das hin­tere const)
  7. Der Wert muss ursprüng­lich nicht kon­stant gewe­sen sein, es reicht wenn die Funk­tion so dekla­riert ist, dass die Rück­gabe const sein soll.
  8. Nicht funk­tio­nie­ren würde value(), weil value() keine Refe­renz auf ein Ele­ment der Liste zurück­gibt, son­dern nur den Wert aus der Liste her­aus­ko­piert. Das ist nicht, was wir wol­len.
  9. Bei­spiel: void foo(int k) const; vgl. auch Fuß­note 5
  10. Genauer: es ste­hen sowohl const als auch nicht-const-Varianten zur Ver­fü­gung.

Trackback: Trackback-URL |  Feed zum Beitrag: RSS 2.0
Thema: Sezierte C++-Käfer

Diesen Beitrag kommentieren.

Kommentar abgeben