Const-Parameter

Nehmen wir an, wir haben ein sehr große und aufwendige Klasse, die alle Programminternas steuert und deswegen überall gelesen werden muss, aber halt nicht überall verändert werden darf. Hier symbolisch dargestellt von

class Data
{
  public: 
 
    int Date;
};

Das Problem

Nehmen wir nun unsere Initialisierungsroutinen, die die Klasse aufwendig zusammenstellt und anschließend einige Funktionen aufruft, die versehentlich die Klasse verändert:

#include <stdio.h>
 
void CallByValue    ( Data   data ) { data.Date  +=   1; }
void CallByValuePtr ( Data * data ) { data->Date +=  20; }
void CallByReference( Data & data ) { data.Date  += 300; }
 
int main( void )
{
  Data data;
  data.Date = 0;
 
  printf( "0 - Datum: %d\n", data.Date ); 
 
  CallByValue( data );
  printf( "1 - Datum: %d\n", data.Date ); 
 
  CallByValuePtr( &data );
  printf( "2 - Datum: %d\n", data.Date ); 
 
  CallByReference( data );   
  printf( "3 - Datum: %d\n", data.Date ); 
 
  return 0;  
}

Im ersten Fall wird CallByValue übergeben, es wird also eine Kopie der Klasse erzeugt, was viel Rechenzeit kostet, aber uns davor bewahrt, dass unsere Instanz verändert werden kann.

Im 2. Fall wird ein Pointer übergeben, um die Rechenzeit zu sparen. Dies führt jedoch dazu, dass die Funktion jetzt tatsächlich weiß, wo unsere Instanz liegt. Die versehentliche Änderung wird nun also wirklich auf unserer Klasse ausgeführt. Selbiges im dritten Fall.

Und so ist die Ausgabe nun wie folgt:

0 - Datum: 0
1 - Datum: 0
2 - Datum: 20
3 - Datum: 320

Obwohl eigentlich gewünscht war, dass sich das Datum nicht ändert, sind zwei Programmierfehler unentdeckt geblieben und haben die Programmeinstellungen verändert.

Die Lösung

Diesem Problem können wir mit Const-Correctness auf die Spur kommen. Funktionen, die ausschließlich lesen können dürfen, erhalten konstante Objekte. Die Funktion CallByValue hingegen möchte mit einer Kopie arbeiten, deswegen ist es sinnvoll, dass sie eine Kopie erhält. Die Funktionen CallByValuePtr und CallByReference sollen keine Änderungen vornehmen dürfen. Daher ändern wir die Signatur der Funktion (die Typen der Parameter) und behaupten, dass sie konstante Objekte erhalten:

void CallByValue    ( Data         data ) { data.Date  +=   1; }
void CallByValuePtr ( Data const * data ) { data->Date +=  20; }
void CallByReference( Data const & data ) { data.Date  += 300; }

Die Behauptung ist, dass das Argument data konstant ist, also kann der Compiler nun prüfen, ob sich der Benutzer auch daran hält. Der GCC findet folgendes heraus:

start.cpp: In function ‘void CallByValuePtr(const Data*)’:
start.cpp:10: error: assignment of data-member ‘Data::Date’ in read-only structure
start.cpp: In function ‘void CallByReference(const Data&)’:
start.cpp:11: error: assignment of data-member ‘Data::Date’ in read-only structure

Das Programm ist also nicht mehr kompilierbar, weil der Entwickler widersprüchliche Anweisungen gibt: Er möchte etwas in einem Objekt ändern, das unveränderlich sein soll. Hier muss man sich also nochmal hinsetzen und überlege, was man eigentlich möchte.

Weitergabe von Argumenten

Wichtig hierbei ist, dass aus einer Konstante keine Variable mehr werden darf. Das bedeutet, dass man eine Variable an eine Funktion übergeben darf, die eine Konstante erwartet. Was aber nicht geht, ist dass man ein als konstant definiertes Objekt an eine Methode übergeben kann, die eine Variable benötigt. Deswegen eignet sich Const-Correctness auch sehr gut, um Denkfehler in der Softwarearchitektur aufzuzeigen.

Das Ziel sollte sein, Const-Correctness so häufig wie möglich zu verwenden und so in der ganzen Software klarzustellen, wer welche Objekte verändern darf und wer nicht.