Konstante Korrektheit – C++-Tutorials (2024)

Konstante Korrektheit – C++-Tutorials (1)
Von Alex Allain

Mit dem Schlüsselwort const können Sie angeben, ob eine Variable änderbar ist oder nicht. Sie können const verwenden, um Änderungen an Variablen und Konstantenzeigern zu verhindern, und Konstantenverweise verhindern, dass die Daten geändert werden, auf die verwiesen wird (oder auf die verwiesen wird).

Aber warum kümmert es dich?

Const gibt Ihnen die Möglichkeit, Ihr Programm klarer zu dokumentieren und diese Dokumentation tatsächlich durchzusetzen. Durch die Durchsetzung Ihrer Dokumentation bietet das Schlüsselwort const Ihren Benutzern Garantien, die es Ihnen ermöglichen, Leistungsoptimierungen vorzunehmen, ohne dass die Gefahr besteht, dass ihre Daten beschädigt werden. Mithilfe von Konstreferenzen können Sie beispielsweise angeben, dass die Daten, auf die verwiesen wird, nicht geändert werden. Dies bedeutet, dass Sie Konstantenreferenzen als einfache und unmittelbare Möglichkeit zur Leistungsverbesserung für jede Funktion verwenden können, die derzeit Objekte als Wert annimmt, ohne sich Sorgen machen zu müssen, dass Ihre Funktion beschädigt wird könnte die Daten ändern. Selbst wenn dies der Fall ist, verhindert der Compiler die Kompilierung des Codes und macht Sie auf das Problem aufmerksam. Wenn Sie andererseits keine Konstantenverweise verwenden würden, hätten Sie keine einfache Möglichkeit sicherzustellen, dass Ihre Daten nicht geändert wurden.

Dokumentation und Sicherheit

Der Hauptzweck der Konstanz besteht darin, Dokumentation bereitzustellen und Programmierfehler zu verhindern. Mit Const können Sie sich selbst und anderen klar machen, dass etwas nicht geändert werden sollte. Darüber hinaus hat es den zusätzlichen Vorteil, dass alles, was Sie als const deklarieren, tatsächlich const bleibt, sofern keine erzwungenen Methoden verwendet werden (über die wir später sprechen werden). Es ist besonders nützlich, Referenzparameter für Funktionen als Konstreferenzen zu deklarieren:

bool verifyObjectCorrectness (const myObj& obj);

Hier wird ein myObj-Objekt per Referenz an verifyObjectCorrectness übergeben. Aus Sicherheitsgründen wird const verwendet, um sicherzustellen, dass „VerifyObjectCorrectness“ das Objekt nicht ändern kann – schließlich soll es nur sicherstellen, dass sich das Objekt in einem gültigen Zustand befindet. Dies kann dumme Programmierfehler verhindern, die andernfalls zu einer Beschädigung des Objekts führen könnten (z. B. indem ein Feld der Klasse zu Testzwecken festgelegt wird, was dazu führen könnte, dass das Feld nie zurückgesetzt wird). Darüber hinaus können Benutzer der Funktion durch die Deklaration des Arguments const sicher sein, dass ihr Objekt nicht geändert wird und sie sich keine Gedanken über die möglichen Nebenwirkungen des Funktionsaufrufs machen müssen.

Syntaxhinweis

Bei der Deklaration einer const-Variablen ist es möglich, const entweder vor oder nach dem Typ zu setzen, also beides

int const x = 5;

Und

const int x = 4;

Dies führt dazu, dass x eine konstante ganze Zahl ist. Beachten Sie, dass in beiden Fällen der Wert der Variablen in der Deklaration angegeben wird; Es gibt keine Möglichkeit, es später einzustellen!

Const-Zeiger

Wir haben bereits die Demonstration von Konstantenreferenzen gesehen, und sie sind ziemlich natürlich: Wenn Sie eine Konstantenreferenz deklarieren, verweisen Sie nur auf die Daten, auf die sich die Konstanten beziehen. Referenzen können ihrer Natur nach nicht ändern, worauf sie verweisen. Zeiger hingegen können auf zwei Arten verwendet werden: Sie können die Daten ändern, auf die verwiesen wird, oder den Zeiger selbst ändern. Folglich gibt es zwei Möglichkeiten, einen const-Zeiger zu deklarieren: eine, die Sie daran hindert, zu ändern, worauf Sie zeigen, und eine, die Sie daran hindert, die Daten zu ändern, auf die gezeigt wird.

Die Syntax zum Deklarieren eines Zeigers auf konstante Daten ist ganz natürlich:

const int *p_int;

Sie können sich das so vorstellen, als würden Sie lesen, dass *p_int ein „const int“ ist. Der Zeiger kann also geändert werden, aber Sie können definitiv nicht berühren, worauf p_int zeigt. Der Schlüssel hier ist, dass die Konstante vor dem * erscheint.

Wenn Sie andererseits nur möchten, dass die im Zeiger selbst gespeicherte Adresse const ist, müssen Sie const nach dem * einfügen:

int x;int * const p_int = &x;

Ich persönlich finde diese Syntax irgendwie hässlich; Aber es gibt keinen anderen offensichtlich besseren Weg, dies zu tun. Man kann es sich so vorstellen, dass „* constp_int“ eine reguläre Ganzzahl ist und dass sich der in p_int selbst gespeicherte Wert nicht ändern kann – Sie können also einfach nicht die Adresse ändern, auf die verwiesen wird. Beachten Sie übrigens, dass dieser Zeiger bei der Deklaration initialisiert werden musste: Da der Zeiger selbst const ist, können wir später nicht ändern, worauf er zeigt! Das sind die Regeln.

Im Allgemeinen bezeichne ich den ersten Zeigertyp, bei dem die Daten unveränderlich sind, als „Konstantenzeiger“ (teilweise, weil dieser Zeigertyp häufiger vorkommt und wir ihn daher auf natürliche Weise beschreiben sollten). ).

Konstante Funktionen

Die Auswirkungen der Deklaration einer Variablen als const breiten sich im gesamten Programm aus. Sobald Sie ein const-Objekt haben, kann es keiner Nicht-Konferenz zugewiesen werden oder Funktionen verwenden, von denen bekannt ist, dass sie den Zustand des Objekts ändern können. Dies ist notwendig, um die Konstanz des Objekts zu erzwingen, aber es bedeutet, dass Sie eine Möglichkeit benötigen, anzugeben, dass eine Funktion keine Änderungen an einem Objekt vornehmen soll. In nicht objektorientiertem Code ist dies genauso einfach wie die Verwendung von Konstantenreferenzen, wie oben gezeigt.

In C++ gibt es jedoch das Problem von Klassen mit Methoden. Wenn Sie über ein const-Objekt verfügen, möchten Sie keine Methoden aufrufen, die das Objekt ändern können. Daher benötigen Sie eine Möglichkeit, dem Compiler mitzuteilen, welche Methoden sicher aufgerufen werden können. Diese Methoden werden „const-Funktionen“ genannt und sind die einzigen Funktionen das kann für ein const-Objekt aufgerufen werden. Beachten Sie übrigens, dass nur Member-Methoden als konstante Methoden sinnvoll sind. Denken Sie daran, dass in C++ jede Methode eines Objekts einen impliziten this-Zeiger auf das Objekt erhält; const-Methoden erhalten effektiv einen const-Zeiger.

Die Möglichkeit zu deklarieren, dass eine Funktion für const-Objekte sicher ist, besteht einfach darin, sie als const zu markieren. Die Syntax für const-Funktionen ist etwas eigenartig, da es nur eine Stelle gibt, an der man const wirklich am Ende der Funktion einfügen kann:

 ::() const{ // ...}

Zum Beispiel,

int Loan::calcInterest() const{ return yield_value * Interest_rate; }

Beachten Sie, dass nur weil eine Funktion als const deklariert ist, nicht verhindert wird, dass Nicht-Const-Funktionen sie verwenden. die Regel ist diese:

  • Const-Funktionen können immer aufgerufen werden
  • Nichtkonstante Funktionen können nur von nichtkonstanten Objekten aufgerufen werden

Das macht Sinn: Wenn Sie eine const-Funktion haben, bedeutet das nur, dass sie garantiert, dass sie das Objekt nicht verändert. Nur weil es const ist, heißt das nicht, dass nicht konstante Objekte es nicht verwenden können.

Tatsächlich unterliegen konstante Funktionen einer etwas stärkeren Einschränkung als lediglich der Tatsache, dass sie die Daten nicht ändern können. Sie müssen dafür sorgen, dass sie nicht auf eine Weise verwendet werden können, die es Ihnen ermöglichen würde, sie zum Ändern von Konstantendaten zu verwenden. Das bedeutet, dass konstante Funktionen, wenn sie Referenzen oder Zeiger auf Mitglieder der Klasse zurückgeben, ebenfalls konstant sein müssen.

Konstante Überladung

Da konstante Funktionen zum großen Teil keine nicht-konstanten Verweise auf die Daten eines Objekts zurückgeben können, kann es oft sinnvoll erscheinen, sowohl konstante als auch nicht-konstante Versionen einer Funktion zu haben. Wenn Sie beispielsweise eine Referenz auf einige Mitgliedsdaten zurückgeben (normalerweise keine gute Sache, aber es gibt Ausnahmen), möchten Sie möglicherweise eine nicht konstante Version der Funktion haben, die eine nicht konstante Referenz zurückgibt:

int& myClass::getData(){ return data;}

Andererseits möchten Sie nicht verhindern, dass jemand eine const-Version Ihres Objekts verwendet.

myClass constDataHolder;

vom Abrufen der Daten. Sie möchten nur verhindern, dass diese Person es ändert, indem Sie eine konstante Referenz zurückgeben. Aber Sie möchten wahrscheinlich nicht, dass sich der Name der Funktion ändert, nur weil Sie ändern, ob das Objekt const ist oder nicht. Dies würde unter anderem bedeuten, dass sich möglicherweise sehr viel Code ändern müsste, nur weil Sie die Art und Weise ändern, wie Sie eine Variable deklarieren. -Der Wechsel von einer Anon-Const-Version zu einer Const-Version einer Variablen würde echte Kopfschmerzen bereiten.

Glücklicherweise ermöglicht C++ eine Überladung basierend auf der Konstanz einer Methode. Sie können also sowohl konstante als auch nicht konstante Methoden verwenden und die richtige Version wird ausgewählt. Wenn Sie in einigen Fällen eine nicht konstante Referenz zurückgeben möchten, müssen Sie lediglich eine zweite konstante Version der Methode deklarieren, die eine konstante Methode zurückgibt:

// wird nur für const-Objekte aufgerufen, da auch eine nicht-const-Version existiertconst int& myData::getData() const{ return data;}

Const-Iteratoren

Wie wir bereits gesehen haben, erfordert C++ zur Durchsetzung der Konstanz, dass Konstfunktionen nur Konstantzeiger und Referenzen zurückgeben. Da Iteratoren auch zum Ändern der zugrunde liegenden Sammlung verwendet werden können, wenn eineSTLWenn die Sammlung als const deklariert wird, müssen alle über die Sammlung verwendeten Iteratoren Constitatoren sein. Sie ähneln normalen Iteratoren, können jedoch nicht zum Ändern der zugrunde liegenden Daten verwendet werden. (SeitIteratorenBereichsverallgemeinerung der Idee von Zeigern, das macht Sinn.)

Const-Iteratoren in der STL sind recht einfach: Hängen Sie einfach „const_“ an den gewünschten Iteratortyp an. Beispielsweise könnten wir wie folgt über einen Vektor iterieren:

std::vector vec;vec.push_back( 3 );vec.push_back( 4 );vec.push_back( 8 );for ( std::vector::const_iterator itr = vec.begin(), end = vec.end(); itr != end; ++itr ){ // einfach die Werte ausdrucken... std::cout<< *itr <<:endl>

Beachten Sie, dass ich einen Const-Iterator verwendet habe, um über eine nicht-const-Sammlung zu iterieren. Warum das tun? Aus dem gleichen Grund, aus dem wir normalerweise const verwenden: Es verhindert die Möglichkeit alberner Programmierfehler („Ups, ich wollte die beiden Werte vergleichen, nicht zuweisen!“) und es dokumentiert, dass wir niemals beabsichtigen, den Iterator zum Ändern zu verwenden Sammlung.

Const-Besetzung

Manchmal haben Sie eine const-Variable und möchten sie unbedingt an eine Funktion übergeben, von der Sie sicher sind, dass sie nicht geändert wird. Aber diese Funktion deklariert ihr Argument nicht als const. (Dies kann beispielsweise passieren, wenn eine Clibrary-Funktion wie strlen ohne Verwendung von const deklariert wurde.) Glücklicherweise gibt die Übergabe einer const-Variablen an eine Funktion, die nicht explizit angibt, dass sie die Daten nicht ändert, sicher an, wenn Sie wissen, dass sie sicher ist , dann können Sie einen const_cast verwenden, um die Konstanz des Objekts vorübergehend zu entfernen.

Const-Casts sehen normal ausTypumwandlungen in C++,mit der Ausnahme, dass sie nur zum Aufheben der Konstanz (oder Flüchtigkeit) verwendet werden können, nicht jedoch zum Konvertieren zwischen Typen oder zum Aufheben einer Klassenhierarchie.

// eine schlechte Version von strlen, die ihr Argument nicht deklariert constint bad_strlen (char *x){ strlen( x );}// Beachten Sie, dass die zusätzliche Konstante tatsächlich in dieser Deklaration implizit ist, da // String-Literale Konstanten sindconst char * x = "abc";// Const-ness für unsere Strlen-Funktion wegwerfen bad_strlen( const_cast(x) );

Beachten Sie, dass Sie const_cast auch für den umgekehrten Weg – addconst-ness – verwenden können, wenn Sie das wirklich möchten.

Effizienzgewinne? Ein Hinweis zur konzeptionellen vs. bitweisen Konstanz

Eine häufige Rechtfertigung für die konstante Korrektheit basiert auf der falschen Vorstellung, dass die konstante Korrektheit als Grundlage für Optimierungen verwendet werden kann. Leider ist dies im Allgemeinen nicht der Fall – selbst wenn eine Variable als const deklariert wird, bleibt sie nicht unbedingt unverändert. Erstens ist es möglich, Konstanz mit einem const_cast wegzuwerfen. Es mag albern erscheinen, wenn Sie einen Parameter für eine Funktion als const deklarieren, aber es ist möglich. Das zweite Problem besteht darin, dass in Klassen aufgrund des Schlüsselworts mutable sogar const-Klassen geändert werden können.

Veränderbare Daten in Const-Klassen

Erstens: Warum sollten Sie jemals die Möglichkeit haben wollen, Daten in einer als const deklarierten Klasse zu ändern? Dies bringt den Kern dessen auf den Punkt, was Konstanz bedeutet, und es gibt zwei Möglichkeiten, darüber nachzudenken. Eine Idee ist die der „bitweisen Konstanz“, was im Grunde bedeutet, dass eine const-Klasse jederzeit genau die gleiche Darstellung im Speicher haben sollte. Leider (oder zum Glück) ist dies nicht das vom C++-Standard verwendete Paradigma; Stattdessen verwendet C++ „konzeptionelle Konstanz“. Konzeptionelle Konstanz bezieht sich auf die Idee, dass die Ausgabe der Klasse const immer gleich sein sollte. Dies bedeutet, dass sich die zugrunde liegenden Daten ändern können, solange das grundlegende Verhalten gleich bleibt. (Im Wesentlichen ist das „Konzept“ konstant, aber die Darstellung kann variieren.)

Warum gibt es konzeptionelle Konstanz?

Warum sollten Sie jemals konzeptionelle Konstanz der bitweisen Konstanz vorziehen? Ein Grund ist die Effizienz: Wenn Ihre Klasse beispielsweise über eine Funktion verfügt, die auf einem Wert basiert, dessen Berechnung viel Zeit in Anspruch nimmt, ist es möglicherweise effizienter, den Wert einmal zu berechnen und ihn dann für spätere Anforderungen zu speichern. Das Verhalten der Funktion wird dadurch nicht verändert – sie gibt immer den gleichen Wert zurück. Es wird jedoch die Darstellung der Klasse ändern, da sie einen Ort zum Zwischenspeichern des Werts benötigen muss.

C++-Unterstützung für konzeptionelle Konstanz

C++ sorgt für konzeptionelle Konstanz durch die Verwendung des Schlüsselworts mutable: Beim Deklarieren einer Klasse können Sie angeben, dass einige der Felder veränderbar sind:

veränderbar int my_cached_result;

Dadurch können konstante Funktionen das Feld ändern, unabhängig davon, ob das Objekt selbst als konstant deklariert wurde oder nicht.

Andere Möglichkeiten, die gleichen Erfolge zu erzielen

Wenn Sie vorhaben, const zur Steigerung der Effizienz zu verwenden, denken Sie darüber nach, was das wirklich bedeuten würde – es wäre so, als würden Sie die Originaldaten verwenden, ohne eine Kopie davon zu erstellen. Wenn Sie dies jedoch tun möchten, wäre der einfachste Ansatz einfach die Verwendung von Referenzen oder Zeigern (vorzugsweise konstante Referenzen oder Zeiger). Dadurch erhalten Sie einen echten Effizienzgewinn, ohne sich auf Compileroptimierungen verlassen zu müssen, die wahrscheinlich nicht vorhanden sind.

Gefahren zu großer Konstanz

Hüten Sie sich davor, const zu sehr auszunutzen. Nur weil Sie beispielsweise eine konstante Referenz zurückgeben können, heißt das nicht, dass Sie eine konstante Referenz zurückgeben sollten. Das wichtigste Beispiel ist, dass Sie, wenn Sie lokale Daten in einer Funktion haben, eigentlich überhaupt keinen Verweis darauf zurückgeben sollten (es sei denn, dies ist der Fall).statisch), da es sich um einen Verweis auf den Speicher handelt, der nicht mehr gültig ist.

Eine andere Möglichkeit, eine konstante Referenz zurückzugeben, ist möglicherweise keine gute Idee, wenn Sie eine Referenz auf Mitgliedsdaten eines Objekts zurückgeben. Obwohl die Rückgabe einer const-Referenz niemanden daran hindert, die Daten durch ihre Verwendung zu ändern, bedeutet dies, dass Sie über persistente Daten verfügen müssen, um die Referenz zu stützen – es muss sich tatsächlich um ein Feld des Objekts und nicht um in der Funktion erstellte temporäre Daten handeln. Sobald Sie den Referenzteil der Schnittstelle zur Klasse erstellt haben, korrigieren Sie die Implementierungsdetails. Dies kann frustrierend sein, wenn Sie später die privaten Daten Ihrer Klasse ändern möchten, sodass das Ergebnis der Funktion beim Aufruf der Funktion berechnet wird und nicht ständig in der Klasse gespeichert wird.

Zusammenfassung

Betrachten Sie const nicht als Mittel zur Steigerung der Effizienz, sondern vielmehr als eine Möglichkeit, Ihren Code zu dokumentieren und sicherzustellen, dass sich einige Dinge nicht ändern können. Denken Sie daran, dass sich die Konstanz im gesamten Programm ausbreitet. Daher müssen Sie Konstantfunktionen, Konstantreferenzen und Konstantiteratoren verwenden, um sicherzustellen, dass es niemals möglich ist, Daten zu ändern, die als „Konstant“ deklariert wurden.

Teile dieses Artikels basierten auf Material vonEffektiveres C++: 35 neue Möglichkeiten zur Verbesserung Ihrer Programme und DesignsKonstante Korrektheit – C++-Tutorials (2)von Scott Meyers undAußergewöhnlicher C++-Stil: 40 neue technische Rätsel, Programmierprobleme und LösungenKonstante Korrektheit – C++-Tutorials (3)von Herb Sutter.

Beliebte Seiten

  • Einstieg in C++, das E-Book von Cprogramming.com
  • So lernen Sie C++ oder C
  • C-Tutorial
  • C++-Tutorial
  • 5 Möglichkeiten, wie Sie lernen können, schneller zu programmieren
  • Die 5 häufigsten Probleme, mit denen neue Programmierer konfrontiert sind
  • So richten Sie einen Compiler ein
  • Wie man ein Spiel in 48 Stunden erstellt

Werbung|Datenschutz-Bestimmungen|Copyright © 2019 Cprogramming.com|Kontakt|Um

Konstante Korrektheit – C++-Tutorials (2024)
Top Articles
Latest Posts
Article information

Author: Aracelis Kilback

Last Updated:

Views: 5873

Rating: 4.3 / 5 (44 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Aracelis Kilback

Birthday: 1994-11-22

Address: Apt. 895 30151 Green Plain, Lake Mariela, RI 98141

Phone: +5992291857476

Job: Legal Officer

Hobby: LARPing, role-playing games, Slacklining, Reading, Inline skating, Brazilian jiu-jitsu, Dance

Introduction: My name is Aracelis Kilback, I am a nice, gentle, agreeable, joyous, attractive, combative, gifted person who loves writing and wants to share my knowledge and understanding with you.