Nebenprobleme

Nebenprobleme

Inhalt


1 const-Modifier von Zeigern in C(++) vs. final in Java

Topic=.UmlJavaAndC.association..

Die Deklaration eines Zeigers mit Type const* pointer oder const Type* pointer bedeutet in C und in C++, dass die Variable pointer durchaus verändert werden kann, die Daten, auf die pointer zeigt, aber nicht. Die Zuweisung

 Type const* pointer;
 pointer = otherPointer;
 

ist also zulässig, nicht aber

 pointer->element = otherValue;  //compile error!
 

Hier hilft die Syntax der Programmiersprache C, Fehler zu vermeiden. Das ist in C elementar wichtig, da die Elemente einer gezeigerten Struktur direkt zugänglich sind. Das const-Zeiger-Prinzip ist insbesondere auch bei Argumente von Funktionen/Methoden wichtig:

 void myFunction(const Type* data);
 

Die Funktion wird die Daten nicht verändern sondern nur lesend darauf zugreifen. - Es gibt ein Problem bzw. wenn man so will eine Konsequenz: Einmal const, immer const. Vorausgesetzt ist hierbei, dass man nicht umcastet. Man kann einem const* eine nicht-const-Referenz zuweisen, aber nicht umgekehrt. Folgendes scheitert:

 void secondFunction(Type* data)
 { myFunction(data);     //das ist ok.         
 }
 //...
 void thirdFunction(Type const* data)
 { secondFunction(myData); //compile error!
 }
 

Im Beispiel ist in der äußeren Schale der Datenzeiger const. Das ist so gewollt. In der inneren Schale, myFunction(const Type* data); ist dies auch so. Aber dazwischen ist das const vergessen oder nicht gewollt. Das geht nicht. Man muss hier Konsequenz walten lassen. Stellt man bei einem Review/Refactoring der Software fest, dass thirdFunction im Beispiel mit const* data arbeiten soll, muss man sich durch alle innen gerufenen Funktionen und Zeigerdeklarationen durchkämpfen.

In C++ hat man diese const-Deklarationsmöglichkeit nicht nur beibehalten, sondern weitergeführt. So kann man selbst ganze Klassen für bestimmte Methoden als const bezeichnen:

 class MyClass
 { methodX() const;
 }
 

Das const nach der Argumentliste der Methodendeklaration bedeutet, dass die Methode in der Klasse keine Werte ändert. Realisierungstechnisch ist der this-Zeiger damit als const deklariert.

In Java dagegen ist const abgeschafft.' Dafür gibt es final. final und const sind etwas sich entsprechend, aber nur etwas. Der Unterschied:

  • final dient der Sicherung, dass ein Element einer Klasse nur an einer Stelle im Konstruktur gesetzt wird und danach nicht mehr verändert. Ein Programmierer kann sich darauf verlassen: Hat er die Stelle gefunden, wo das Element gesetzt ist, weiß er genau, welchen Wert es haben wird. final kann man sowohl auf Variable, die dann konstant sind, als auch auf Referenzen anwenden. Im Konstruktor gesetzte Referenzen sind nach UML-Verständnis entweder Compositionen oder Aggregationen, je nach dem der Speicher hier selbst beschafft wird oder eine übergebene Referenz gespeichert wird. Insoweit ähnelt final dem const in C++. Man hat in Java aber die Möglichkeit, das Element an beliebiger Stelle im Konstruktor zu setzen. In C++ muss man ein const-Element in der Initialisierungsliste des Konstruktors setzen, was die programmtechnischen Möglichkeiten doch etwas einschränkt.

  • final kann in Java auch für stacklokale Variable oder Methodenargumente verwendet werden. Letzteres sieht zwar ähnlich wie in C(++) aus, jedoch bewirkt final an einer Referenz, dass die Referenzvariable selbst nicht geändert werden kann, jedoch die gezeigerte Referenz. final enspricht also nicht dem Type const*, sondern dem Type* const.

  • Also gibt es in Java überhaupt gar keine Möglichkeit, zu kennzeichnen, dass auf eine Referenz nur lesend zugegriffen werden kann!

Ist das gut so? Die Sicherung der Datenkonsitenz ist nach dem Objektorientierten Ansatz damit gegeben, dass die Daten (im allgemeinen) als private oder protected gekapselt sind, und nur mit bestimmten Methoden darauf zugegriffen werden kann. Manche Methoden sind laut ihrer Beschreibung und Implementierung nur lesend, andere verändern die Daten. Man kann also feingradualer unterscheiden, wie lesend und wie verändernd gearbeitet werden kann.

Dazu kommt noch ein weiteres Konzept: Das der Interfaces. Hat man eine Klasse mit soundsoviel lesenden und soundsoviel schreibenden Methoden, man möchte Instanzen dieser Klasse für die Weiterverarbeitung aber nur lesend anbieten, dann benutzte man ein entsprechendes Interface (wie das sowieso üblich ist), dass nur die lesenden Methoden enthält.

Das ist die Strategie der ObjectOrientierung, manifestiert in Java und auch in der UML so gelebt. Also braucht man hier nicht das (veraltete) const-Konzept von C.

Dennoch kann sich eine Programmierung in C++, auch wenn sie einen grundsätzlichen objektorientierten Ansatz hat, an wichtigen Stellen nicht nach dem ObjectOrientierten Paradima richten. Und zwar dort, wo direkt auf Daten zugegriffen werden soll, ohne Methoden und Kapselung, weil es sich um C-Strukturen (auch Plain Old Data) handelt. Das passiert in C++, weil man tief in die Systemebene runter muss/will.

Also sollte folgende Regel gelten:

  • Bei Klassen, die ihre Attribute und Assoziationen kapseln, kann auf const* verzichtet werden. Man hat in C++ zwar nicht das final-Konzept zur Sicherung der Belegung von Elementen nur im Konstruktor, das kann aber mit Einhaltung von Programm-Richtlinien (manueller Kontrolle) wett gemacht werden.

  • Bei Strukturen bzw. Klassen mit public-Elementen muss const eingesetz werden, wo es notwendig ist.

Interessant ist es, dass sich auch ein UML-Tool, dass für Embedded und C/C++ favorisiert wird: Rhapsody (Telelogic) sich genau nach diesem Konzept richtet. Es gibt dort keine Property, um Assoziationen als const zu bezeichnen. Das ist laut obiger Aussage auch nicht notwendig. Wenn man mit struct (POD) arbeitet, gibt es dort die Möglichkeit der Einbindung von externen Headerfiles als passende Möglichkeit.