Parameterübergabe an Funktionen

Martin Kompf

Parameter lassen sich in C++ an Funktionen per value, per pointer oder per reference übergeben. Nach welchen Kriterien soll der Programmierer nun die richtige Auswahl treffen?

Obwohl es in C möglich ist, Funktionen wie

void abort();

zu verwenden, die weder Parameter noch Rückgabewert haben, werden die meisten eingetzten Funktionen doch einen oder mehrere Parameter aufweisen müssen, um etwas Sinnvolles tun zu können. Zum Beispiel könnte die Funktion

int sign( int i);

dazu dienen, das Vorzeichen der als Parameter übergebenen Zahl zu ermitteln.

Bei Wertübergabe wird kopiert

Dabei wird der Funktionsparameter i (bzw. die beim Aufruf aktuell übergebene Variable) per value (als Wert) an die Funktion übergeben. Das heisst, dass während des Ablaufs der Funktion sign() eine Kopie der Variablen auf dem Stack des Prozessors angelegt wird. Innerhalb der Funktion sign() kann deshalb mit dem Funktionsparameter i was auch immer angestellt werden - nach der Beendigung von sign(a) kann der Programmierer sich darauf verlassen, dass a auf jeden Fall den gleichen Wert wie vorher hat:

int sign( int i) {
    if (i > 0) i = 1;
    if (i < 0) i = -1;
    return i;
}

a = -5;
cout << sign(a) << endl;
cout << a << endl; // a ist immer noch -5!

Es kann nur Einen (Rückgabewert) geben!

Dieses - im vorliegenden Fall durchaus erwünschte - Verhalten führt dann zu Problemen, wenn eine Funktion mehr als einen Wert an das aufrufende Programm zurückgeben soll - es kann schließlich nur einen einzigen Rückgabewert geben! Was ist also zu tun, wenn zum Beispiel eine Funktion swap() geschrieben werden soll, die zwei Integerwerte miteinander vertauscht? Die typische C-Lösung ist, dass nicht die Integerwerte an sich, sondern nur deren Adressen an die Funktion übergeben werden. Die Parameterübergabe erfolgt dann per pointer:

void swap( int *i1, int *i2) {
    int t;
    t = *i1; *i1 = *i2; *i2 = t;
}

int a, b;
a = 1; b = 2;

swap( &a, &b);
cout << a << " " << b << endl;

funktioniert zwar bestens, ist aber wahrscheinlich mit ein Grund dafür, dass sich manch ein lernwilliger C-Anfänger nach dieser Lektion mit Grausen abgewendet hat.

Referenzen: Nützlich für viele Zwecke

C++ hat für diese Zwecke nun eine weitere Form der Parameterübergabe, die Übergabe per reference eingeführt. Diese wird dadurch kenntlich gemacht, dass dem Funktionsparameter ein & vorangestellt wird, welches in diesem Kontext bitte nicht mit dem Adressoperator & verwechselt werden darf! Obiges swap()-Beispiel würde unter Verwendung von Referenzen so aufgeschrieben werden können:

void swap( int &i1, int &i2) {
    int t;
    t = i1; i1 = i2; i2 = t;
}

int a, b;
a = 1; b = 2;

swap( a, b);
cout << a << " " << b << endl;

Die ganze Passage ist wesentlich besser lesbar; die Parameterübergabe per reference ist bei der Verwendung von C++ in jedem Fall der per pointer vorzuziehen!

Das in den bisherigen Beispielen Gesagte trifft auch dann zu, wenn es sich bei den Übergabeparametern statt um einfache Typen (wie int, float oder double) um Objekte handelt:

class A {
// ...
};

int count( A a);

Hierbei wird ein Objekt vom Typ A per value an die Funktion count() übergeben. Weiter oben wurde gesagt, dass bei dieser Form der Übergabe eine Kopie des Parameters auf dem Stack angelegt wird. Während bei einfachen Typen wie int der Prozessor weiss, wie er eine Kopie anlegen muss, gestaltet sich das Kopieren bei Objekten wesentlich komplizierter. Hier wird nämlich der Copy-Konstruktor der Klasse A aufgerufen!. Ein fehlender oder falsch programmierter Copy-Konstruktor kann in dieser Situation richtig Ärger verursachen. Wenn kein Copy-Konstruktur vom Programmierer explizit definiert wurde, dann erzeugt der C++-Compiler zwar selbst einen solchen - dass dieser dann aber das Richtige tut, ist nicht selbstverständlich (siehe dazu auch die Erklärungen im C++-Tutorial).

Kopieren kostet Zeit!

Neben der Notwendigkeit des Vorhandenseins eines korrekten Copy-Konstruktors kommt bei der Übergabe von Objekten per Value noch ein zweiter Aspekt zum Tragen: Unter Umständen kann ein Objekt sehr viele Daten enthalten - man denke nur an einen Textstring, der diesen Artikel hier (mit über 8000 Zeichen) speichert. Diese Daten müssen jedesmal bei einer Wertübergabe kopiert werden - das kostet wertvolle Zeit! Daher bietet sich auch in diesem Falle die Übergabe per reference an. Der Tatsache, dass nun ja die Funktion die per reference übergebene Variable auch im aufrufenden Kontext ändern könnte, lässt sich durch zusätzliche Verwendung des Schlüsselwortes const ein Riegel vorschieben (siehe dazu den Artikel Konstanten). Eine Funktionsdeklaration der Form

int count( const string &a);

spart also das Kopieren des Strings a bei jedem Funktionsaufruf, stellt jedoch sicher, dass a innerhalb von count() nicht verändert werden kann.

Dieses Vorgehen lässt sich analog auf die Verwendung von Pointern als Funktionsparameter übertragen, die meisten Stringfunktionen aus der C Standardbibliothek profitieren davon. So wie zum Beispiel die Funktion

int strcmp(const char* cs, const char* ct);

zum Vergleich zweier Zeichenketten ein (sinnloses) Kopieren von cs und ct vermeidet, durch die Angabe von const jedoch dem Anwender garantiert, dass seine per pointer übergebenen Variablen durch strcmp() nicht angetastet werden.