Ich selbst habe mich lange davor gedrückt, aber irgendwann wurde es doch Zeit mich mit dem const
-Keyword in C++ außeinanderzusetzen. Und um dieses Keyword soll es in diesem Beitrag auch gehen.
Was ist const?
Was ist const
und wozu braucht man es? Naja, const
sagt einfach nur aus, dass eine Variable nicht verändert werden darf. Oder dass eine Funktion die Daten nicht verändert. Nun gibt es in C++ aber auch Pointer und das macht das ganze etwas komplizierter.
const ist ein Trailing-Keyword
Das heißt es steht immer hinter dem Code, auf den es sich bezieht.
Beispiel:
// const bezieht sich auf den Typ (int) int const *i; // const bezieht sich auf den Pointer int * const i; // das erste const bezieht sich auf den Typ (int), das zweite auf den Pointer int const * const i;
Am besten liest man das ganze von rechts nach links:
- Zeile 2: i ist ein Pointer auf const int
- Zeile 4: i ist ein const Pointer auf int
- Zeile 6: i ist ein const Pointer auf const int
Ihr habt bestimmt auch schon die Schreibweise const int i
oder const int &i
gesehen. Warum steht hier const
vor dem Typen, obwohl es sich um ein Trailing-Keyword handelt?
Da sich links vom Typen nichts mehr befindet, kann sich const
auch auf nichts anderes beziehen. In dem Fall darf const
also auch links vom Typ stehen. Folgende beiden Zeilen sind also gleichbedeutend:
const int i; int const i;
const bei Variablen und Pointern
Sehen wir uns ein paar Beispiele für das const
Keyword bei Pointern und Variablen an:
// const int: //------------------------------------------- int const i = 5; // Fehler: Der int ist const und kann somit nicht geaendert werden i = 10; // const Pointer auf int //------------------------------------------- int * const i = new int(5); // Erfolg: Nur der Pointer ist const, der Wert des int kann somit geaendert werden *i = 10; // Fehler: Der Pointer ist const und kann deshalb nicht geaendert werden i = new int(10); // Pointer auf const int //------------------------------------------- int const * i = new int(5); // Erfolg i = new int(10); // Fehler *i = 10; // const Pointer auf einen const int: //------------------------------------------- int * const * i = new int(5); // Fehler: int ist const und darf nicht geaendert werden *i = 10; // Fehler: Der Pointer ist ebenfalls const und darf nicht geaendert werden i = new int(10);
Das ganze funktioniert logischerweise auch mit Smart-Pointern. Hier mal am Beispiel eines unique_ptr
:
std::unique_ptr<int const> p = std::make_unique<int>(5); // Erfolg p = std::make_unique<int>(10); // Fehler *p = 10; std::unique_ptr<int const> const p = std::make_unique<int>(5); // Fehler p = std::make_unique<int>(10); // Fehler *p = 10;
Was ist mit Funktionen?
Auch Funktionen, die Daten nicht verändern sollten als const
gekennzeichnet werden um const-correctness zu „erreichen“. Wird eine Funktion, die keine Daten verändert nicht als const
gekennzeichnet, kann sie auch nicht von anderen Funktionen aufgerufen werden, die als const
gekennzeichnet wurden.
Ein kleines Beispiel:
class Person { private: std::string name_{""}; public: Person(std::string name) : name_(name) {} void print() { std::cout << this->name_ << std::endl; } void printConst() const { std::cout << this->name_ << std::endl; } }; // Erfolg: Person in printPerson + Person::print ist beides nicht const void printPerson(Person &person) { person.print(); } // Fehler: Person in printPerson ist const, Person::print aber nicht void printPerson(const Person &person) { person.print(); } // Erfolg: Person in printPerson ist const, Person::printConst ist ebenfalls const void printPerson(const Person &person) { person.printConst(); }
Es gibt übrigens auch die Möglichkeit die Funktion zwei mal (einmal const, einmal nicht const) zu definieren. Der Compiler wählt denn die entsprechend benötigte Funktion:
class Person { private: std::string name_{""}; bool printed_{false}; public: Person(std::string name) : name_(name) {} void print() { std::cout << this->name_ << std::endl; this->printed_ = true; std::cout << this->printed_ << std::endl; } void print() const { std::cout << this->name_ << std::endl; std::cout << this->printed_ << std::endl; } };