Wie viele und welche Verwendungszwecke hat "const" in C ++?

129

Als unerfahrener C ++ - Programmierer gibt es einige Konstrukte, die für mich immer noch sehr dunkel aussehen. Eines davon ist const. Sie können es an so vielen Orten und mit so vielen verschiedenen Effekten verwenden, dass es für Anfänger fast unmöglich ist, lebend herauszukommen. Wird ein C ++ - Guru einmal für immer die verschiedenen Verwendungszwecke erklären und ob und / oder warum sie nicht verwendet werden sollen?

tunnuz
quelle
genau auf der Suche nach dieser Frage: D
Alamin

Antworten:

100

Der Versuch, einige Verwendungen zu sammeln:

Binden Sie einige temporäre an Verweis auf const, um seine Lebensdauer zu verlängern. Die Referenz kann eine Basis sein - und der Destruktor muss nicht virtuell sein - der richtige Destruktor heißt immer noch:

ScopeGuard const& guard = MakeGuard(&cleanUpFunction);

Erklärung mit Code:

struct ScopeGuard { 
    ~ScopeGuard() { } // not virtual
};

template<typename T> struct Derived : ScopeGuard { 
    T t; 
    Derived(T t):t(t) { }
    ~Derived() {
        t(); // call function
    }
};

template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }

Dieser Trick wird in der Dienstprogrammklasse ScopeGuard von Alexandrescu verwendet. Sobald das temporäre Element den Gültigkeitsbereich verlässt, wird der Destruktor von Derived korrekt aufgerufen. Dem obigen Code fehlen einige kleine Details, aber das ist die große Sache.


Verwenden Sie const, um anderen Methoden mitzuteilen, dass der logische Status dieses Objekts nicht geändert wird.

struct SmartPtr {
    int getCopies() const { return mCopiesMade; }
};

Verwenden Sie const für Copy-on-Write-Klassen , damit der Compiler Ihnen bei der Entscheidung hilft, wann und wann Sie nicht kopieren müssen.

struct MyString {
    char * getData() { /* copy: caller might write */ return mData; }
    char const* getData() const { return mData; }
};

Erläuterung : Möglicherweise möchten Sie Daten freigeben, wenn Sie etwas kopieren, solange die Daten des ursprünglichen und des kopierten Objekts gleich bleiben. Sobald eines der Objekte Daten ändert, benötigen Sie jedoch zwei Versionen: eine für das Original und eine für die Kopie. Das heißt, Sie kopieren auf eine Schreiben in eines der Objekte, sodass beide jetzt ihre eigene Version haben.

Code verwenden :

int main() {
    string const a = "1234";
    string const b = a;
    // outputs the same address for COW strings
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Das obige Snippet druckt dieselbe Adresse auf meinem GCC, da die verwendete C ++ - Bibliothek eine Kopie beim Schreiben implementiert std::string. Obwohl beide Zeichenfolgen unterschiedliche Objekte sind, verwenden sie denselben Speicher für ihre Zeichenfolgendaten. Wenn Sie bnon-const machen, wird die non-const-Version von bevorzugt, operator[]und GCC erstellt eine Kopie des Sicherungsspeicherpuffers, da wir ihn ändern könnten und er die Daten von nicht beeinflussen darf a!

int main() {
    string const a = "1234";
    string b = a;
    // outputs different addresses!
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Damit der Kopierkonstruktor Kopien von const-Objekten und temporären Objekten erstellt :

struct MyClass {
    MyClass(MyClass const& that) { /* make copy of that */ }
};

Zum Erstellen von Konstanten, die sich trivial nicht ändern können

double const PI = 3.1415;

Zum Übergeben beliebiger Objekte als Referenz anstelle von Wert - um möglicherweise teures oder unmögliches Übergeben von Nebenwerten zu verhindern

void PrintIt(Object const& obj) {
    // ...
}
Johannes Schaub - litb
quelle
2
Können Sie bitte die erste und die dritte Verwendung in Ihren Beispielen erklären?
Tunnuz
"Um dem Angerufenen zu garantieren, dass der Parameter nicht NULL sein kann" Ich sehe nicht, wie const etwas mit diesem Beispiel zu tun hat.
Logan Capaldo
Ups, ich scheitere so. Ich fing irgendwie an, über Referenzen zu schreiben. Vielen Dank für das Stöhnen :) Ich werde das Zeug jetzt natürlich entfernen :)
Johannes Schaub - litb
3
Bitte erläutern Sie das erste Beispiel. Macht für mich nicht viel Sinn.
Chikuba
28

Es gibt wirklich zwei Hauptverwendungen von const in C ++.

Konstante Werte

Wenn ein Wert die Form einer Variablen, eines Elements oder eines Parameters hat, der während seiner Lebensdauer nicht geändert wird (oder nicht geändert werden sollte), sollten Sie ihn als const markieren. Dies hilft, Mutationen am Objekt zu verhindern. Zum Beispiel muss ich in der folgenden Funktion die übergebene Student-Instanz nicht ändern, damit ich sie als const markiere.

void PrintStudent(const Student& student) {
  cout << student.GetName();
}

Warum würden Sie das tun? Es ist viel einfacher, über einen Algorithmus nachzudenken, wenn Sie wissen, dass sich die zugrunde liegenden Daten nicht ändern können. "const" hilft, garantiert aber nicht, dass dies erreicht wird.

Das Drucken von Daten auf cout erfordert natürlich nicht viel Nachdenken :)

Markieren einer Mitgliedsmethode als const

Im vorherigen Beispiel habe ich Student als const markiert. Aber woher wusste C ++, dass das Aufrufen der GetName () -Methode für Schüler das Objekt nicht mutieren würde? Die Antwort ist, dass die Methode als const markiert wurde.

class Student {
  public:
    string GetName() const { ... }
};

Das Markieren einer Methode "const" bewirkt zwei Dinge. In erster Linie teilt es C ++ mit, dass diese Methode mein Objekt nicht mutiert. Die zweite Sache ist, dass alle Mitgliedsvariablen jetzt so behandelt werden, als ob sie als const markiert wären. Dies hilft, hindert Sie jedoch nicht daran, die Instanz Ihrer Klasse zu ändern.

Dies ist ein äußerst einfaches Beispiel, aber hoffentlich hilft es Ihnen bei der Beantwortung Ihrer Fragen.

JaredPar
quelle
16

Achten Sie darauf, den Unterschied zwischen diesen 4 Erklärungen zu verstehen:

Die folgenden 2 Deklarationen sind semantisch identisch. Sie können ändern, wohin ccp1 und ccp2 zeigen, aber Sie können nicht ändern, auf was sie zeigen.

const char* ccp1;
char const* ccp2;

Als nächstes ist der Zeiger const. Um sinnvoll zu sein, muss er initialisiert werden, um auf etwas zu zeigen. Sie können es nicht auf etwas anderes verweisen lassen, aber das, worauf es zeigt, kann geändert werden.

char* const cpc = &something_possibly_not_const;

Schließlich kombinieren wir beide - so kann das Objekt, auf das gezeigt wird, nicht geändert werden, und der Zeiger kann nicht auf eine andere Stelle zeigen.

const char* const ccpc = &const_obj;

Die Spiralregel im Uhrzeigersinn kann dabei helfen, eine Deklaration zu entwirren http://c-faq.com/decl/spiral.anderson.html

Steve Folly
quelle
Auf Umwegen ja, das tut es! Die Spiralregel im Uhrzeigersinn beschreibt es besser - beginnen Sie mit dem Namen (kpPointer) und zeichnen Sie eine Spirale im Uhrzeigersinn, die durch den Token geht, und sagen Sie jeden Token. Natürlich gibt es rechts von kpPointer nichts, aber es funktioniert immer noch.
Steve Folly
3

Als kleine Anmerkung, wie ich hier lese , ist es nützlich, das zu bemerken

const gilt für alles, was sich unmittelbar links von ihm befindet (außer wenn dort nichts vorhanden ist; in diesem Fall gilt es für alles, was sich unmittelbar rechts davon befindet).

JoePerkins
quelle