C++: Klassen
Klassen werden in C++ mit dem Schlüsselwort class
definiert. Dabei müssen Klassendefinitionen immer
mit einem Semikolon abgeschlossen werden(!). Bei Trennung von Deklaration und Implementierung muss die
Definition der Methoden mit dem Namen der Klasse als Namespace erfolgen:
// .h
class Fluppie {
public:
int wuppie(int c=0);
};
// .cpp
int Fluppie::wuppie(int c) { ... }
Die Sichtbarkeiten für die Attribute und Methoden werden blockweise definiert. Für die Klassen selbst gibt es keine Einstellungen für die Sichtbarkeit.
Objekt-Layout: Die Daten (Attribute) liegen direkt im Objekt (anderenfalls Pointer nutzen). Sofern der
Typ der Attribute eine Klasse ist, kann man diese Attribute nicht mit NULL
initialisieren (kein Pointer,
keine Referenz).
Für den Aufruf eines Konstruktors ist kein new
notwendig, es sei denn, man möchte das neue Objekt
auf dem Heap haben (inkl. Pointer auf das Objekt).
Beachten Sie den Unterschied der Initialisierung der Attribute bei einer Initialisierung im Body des Konstruktors vs. der Initialisierung über eine Initialisierungsliste. (Nutzen Sie in C++ nach Möglichkeit Initialisierungslisten.)
- (K2) Attribute von C++-Klassen sind Speicherplatz im Objekt
- (K2) Explizite Konstruktoren
- (K2) Problematik mit Defaultkonstruktoren/-operatoren (Pointer)
- (K3) Konstruktoren (eigene, Default)
- (K3) Unterschied Initialisierungslisten vs. Initialisierung im Body
OOP in C++
public abstract class Dummy {
public Dummy(int v) { value = v; }
public abstract int myMethod();
private int value;
}
class Dummy {
public:
Dummy(int v = 0);
int myMethod();
virtual ~Dummy();
private:
int value;
};
OOP in C++: Unterschiede zu Java
- Klassendefinition muss mit Semikolon beendet werden
- Sichtbarkeit wird immer blockweise eingestellt (per Default immer
private
) - Wie bei Funktionen: Deklaration muss vor Verwendung (= Aufruf) bekannt sein
this
ist keine Referenz, sondern ein Pointer auf das eigene Objekt
Objektlayout: Java vs. C++
Java: Referenzen auf Objekte
class Student {
String name;
Date birthday;
double credits;
}
In Java werden im Objektlayout lediglich die primitiven Attribute direkt gespeichert.
Für Objekte wird nur eine Referenz auf die Objekte gehalten. Die Attribute selbst liegen aber außerhalb der Klasse, dadurch benötigt das Objekt selbst nur relativ wenig Platz im Speicher.
C++: Alles direkt im Objekt
class Student {
string name;
Date birthday;
double credits;
};
In C++ werden alle Attribute innerhalb des Objektlayouts gespeichert. Ein Objekt mit vielen oder großen Feldern braucht also auch entsprechend viel Platz im Speicher.
Wollte man eine Java-ähnliche Lösung aufbauen, müsste man in C++ entsprechend Pointer einsetzen:
class Student {
private:
string *name;
Date *birthday;
double credits;
}
Warum nicht Referenzen?
Objekte erzeugen mit Konstruktoren
class Dummy {
public:
Dummy(int c=0) { credits = c; }
private:
int credits;
};
Erzeugen neuer Objekte:
Dummy a;
Dummy b(37);
Dummy c=99;
=> Kein Aufruf von new
!
(new
würde zwar auch ein neues Objekt anlegen, aber auf dem Heap!)
Default-Konstruktoren
Der C++-Compiler generiert einen parameterlosen Defaultkonstruktor - sofern man nicht selbst mindestens einen Konstruktor definiert.
Dieser parameterlose Defaultkonstruktor wendet für jedes Attribut dessen parameterlosen Konstruktor an, für primitive Typen erfolgt keine garantierte Initialisierung!
Achtung: Default-Konstruktor wird ohne Klammern aufgerufen!
Dummy a; // Korrekt
Dummy a(); // FALSCH!!! (Deklaration einer Funktion `a()`, die ein `Dummy` zurueckliefert)
C++: Trennung .h und .cpp
// .h
class Dummy {
public:
Dummy(int c=0);
private:
int credits;
};
// .cpp
Dummy::Dummy(int c) {
credits = c;
}
Klassenname ist der Scope für die Methoden
Konstruktoren: Normale (Java-like) Initialisierung
class Student {
public:
Student(const string &n, const Date &d, double c) {
name = n;
birthday = d;
credits = c;
}
private:
string name;
Date birthday;
double credits;
};
Hier erfolgt die Initialisierung in zwei Schritten:
- Attribut wird angelegt und mit Defaultwert/-konstruktor des Datentyps initialisiert
- Anschließend wird die Zuweisung im Body des Konstruktors ausgeführt
Das klappt natürlich nur, wenn es einen parameterlosen Konstruktor für das Attribut gibt.
Beispiel oben:
Beim Anlegen von birthday
im Speicher wird der Defaultkonstruktor für
Date
aufgerufen. Danach wird im Body der übergebene Datumswert zugewiesen.
Konstruktoren: Initialisierungslisten
class Student {
public:
Student(const string &n, const Date &d, double c)
: name(n), birthday(d), credits(c)
{}
private:
string name;
Date birthday;
double credits;
};
In diesem Fall erfolgt die Initialisierung in nur einem Schritt:
- Attribut wird angelegt und direkt mit übergebenen Wert (Kopie) initialisiert
Das klappt natürlich nur, wenn ein passender Konstruktor für das Attribut existiert.
Achtung: Die Reihenfolge der Auswertung der Initialisierungslisten wird durch die Reihenfolge der Attribut-Deklarationen in der Klasse bestimmt!!!
Beispiel oben:
Beim Anlegen von birthday
im Speicher wird direkt der übergebene Wert kopiert.
Zwang zu Initialisierungslisten
In manchen Fällen muss man die Initialisierung der Attribute per Initialisierungsliste durchführen.
Hier einige Beispiele:
-
Attribut ohne parameterfreien Konstruktor
Bei "normaler" Initialisierung würde zunächst der parameterfreie Konstruktor für das Attribut aufgerufen, bevor der Wert zugewiesen wird. Wenn es keinen parameterfreien Konstruktor für das Attribut gibt, bekommt man beim Kompilieren einen Fehler.
-
Konstante Attribute
Bei "normaler" Initialisierung würde das Attribut zunächst per parameterfreiem Konstruktor angelegt (s.o.), danach existiert es und ist konstant und darf nicht mehr geändert werden (müsste es aber, um die eigentlich gewünschten Werte im Body zu setzen) ...
-
Attribute, die Referenzen sind
Referenzen müssen direkt beim Anlegen initialisiert werden.
C++11 und delegierende Konstruktoren
class C {
// 1: Normaler Konstruktor
C(int x) { }
// 2: Delegiert zu (1)
C() : C(42) { }
// 3: Rekursion mit (4)
C(char c) : C(42.0) { }
// 4: Rekursion mit (3)
C(double d) : C('a') { }
};
Delegierende Konstruktoren gibt es ab C++11:
- Vor C++11: Ein Objekt ist fertig konstruiert, wenn der Konstruktor durchgelaufen ist
- Ab C++11: Ein Objekt ist fertig konstruiert, wenn der erste Konstruktor fertig ausgeführt ist => Jeder weitere aufgerufene Konstruktor agiert auf einem "fertigen" Objekt.
- Vorsicht mit rekursiven Aufrufen: Compiler kann warnen, muss aber nicht.
C++ und explizite Konstruktoren
-
Implizite Konvertierung mit einelementigen Konstruktoren:
class Dummy { public: Dummy(int c=0); }; Dummy a; a = 37; // Zuweisung(!)
Auf der linken Seite der Zuweisung steht der Typ
Dummy
, rechts einint
. Der Compiler sucht nach einem Weg, aus einemint
einenDummy
zu machen und hat durch die Gestaltung des Konstruktors vonDummy
diese Möglichkeit. D.h. in dieser Zuweisung wird implizit aus der 37 ein Objekt vom TypDummy
gebaut (Aufruf des Konstruktors) und dann die Zuweisung ausgeführt.Dieses Verhalten ist in vielen Fällen recht praktisch, kann aber auch zu unerwarteten Problemen führen. Zur Abhilfe gibt es das Schlüsselwort
explicit
. -
Falls unerwünscht: Schlüsselwort
explicit
nutzenexplicit Dummy(int c=0);
Wrap-Up
- Klassendefinition mit Semikolon abschließen (!)
- Sichtbarkeiten blockweise, keine für Klasse
- Daten liegen direkt im Objekt (anderenfalls Pointer nutzen)
- Attribute sind echte Objekte: Initialisieren mit
NULL
nicht möglich - Konstruktoren: Kein
new
nötig (würde Objekt auf Heap anlegen und Pointer liefern)
C++: Klassen
Erklären Sie die Unterschiede zwischen den Klassendefinitionen (Java, C++):
class Student {
private String name;
private Date birthday;
private double credits;
}
class Student {
private:
string name;
Date birthday;
double credits;
};
Konstruktoren
- Wie kann der implizite Aufruf eines Konstruktors verhindert werden
(beispielsweise in
Dummy b; b=3;
)? - In welchen Fällen muss eine Initialisierung von Attributen in der Initialisierungsliste stattfinden?
- Wie können/müssen
static
Attribute initialisiert werden?
- [Breymann2011] Der C++ Programmierer
Breymann, U., Hanser, 2011. ISBN 978-3-446-42691-7. - [cppreference.com] C and C++ Reference
, cppreference.com. - [cprogramming.com] C Programming and C++ Programming
Allain, A. und Hoffer, A..