Die unglaubliche Const-Referenz, die nicht Const ist

Veröffentlicht Juli 13, 2018 – 23 Bemerkungen

 Täglich C++

Während der Arbeit an der NamedType-Bibliothek stieß ich auf eine Situation, die mich fassungslos machte: eine const-Referenz, die die Änderung des Objekts ermöglicht, auf das sie verweist. Ohne const_cast. Ohne mutable. Ohne irgendetwas im Ärmel.

Wie kann das sein? Und wie erzwinge ich const in dieser const Referenz?

Eine const-Referenz, die nicht const

Hier ist eine Beschreibung einer Situation, in der diese seltsame Referenz auftaucht. Betrachten Sie die folgende Wrapper-Klasse:

template<typename T>class Wrapper{public: Wrapper(T const& value) : value_(value) {} T const& get() const { return value_; } private: T value_;};

Diese Wrapper -Klasse speichert einen Wert vom Typ T und ermöglicht den Zugriff darauf über die get() -Methode (unabhängig davon, wenn Sie sich fragen, ob “get” ein guter Name ist, möchten Sie vielleicht diese detaillierte Diskussion lesen). Der wichtige Teil ist, dass die get() -Methode nur einen schreibgeschützten Zugriff auf den gespeicherten Wert gewährt, da sie eine const-Referenz zurückgibt.

Um dies zu überprüfen, instanziieren wir die Vorlage beispielsweise mit int . Der Versuch, den gespeicherten Wert über die get() -Methode zu ändern, schlägt fehl (versuchen Sie, auf die Schaltfläche Ausführen unten zu klicken).

Was in Ordnung ist, weil wir wollen, dass get() schreibgeschützt ist.

Aber lassen Sie uns jetzt Wrapper mit int& instanziieren. Ein Anwendungsfall hierfür könnte sein, eine Kopie zu vermeiden oder mögliche Änderungen von a zu verfolgen. Versuchen wir nun, den gespeicherten Wert über die von get() zurückgegebene const-Referenz zu ändern:

Und wenn Sie die RUN-Taste drücken, werden Sie sehen, dass … es kompiliert! Und es druckt den Wert, der durch die const Referenz geändert wurde, als wäre nichts Besonderes passiert.

Ist das nicht verwirrend? Spüren Sie nicht den beruhigenden Eindruck von Sicherheit, den const um uns herum vermittelt? Wem können wir vertrauen?

Versuchen wir zu verstehen, was mit dieser Referenz los ist.

Const Referenz oder Referenz auf const?

Der Kern der Sache ist, dass unsere a-Referenz eine const-Referenz ist, aber keine Referenz auf const .

Um zu verstehen, was das bedeutet, zerlegen wir Schritt für Schritt, was in der Template-Instanziierung passiert.

Die get() -Methode gibt ein const T& zurück, wobei T von template T stammt. In unserem zweiten Fall ist T int&, also const T& ist const (int&) &.

Schauen wir uns das genauer an const (int&). int& ist eine Referenz, die auf eine int verweist und diese int ändern darf. Aber const (int&) ist eine Referenz int&, die const ist, was bedeutet, dass die Referenz selbst nicht geändert werden kann.

Was bedeutet es, zunächst eine Referenz zu ändern? Theoretisch würde es bedeuten, dass es sich auf ein anderes Objekt bezieht oder auf nichts. Beide Möglichkeiten sind jedoch in C ++ illegal: Eine Referenz kann nicht erneut gebunden werden. Es bezieht sich während seines gesamten Lebens auf dasselbe Objekt.

Also const zu sein, sagt nicht viel für eine Referenz aus, da sie immer const sind, da sie nicht neu binden können. Dies bedeutet, dass const (int&) effektiv der gleiche Typ wie int& ist.

Was uns mit get() zurückgibt (int&) & . Und nach den Regeln des Zusammenbrechens von Referenzen ist dies int& .

Also get gibt ein int& zurück. Seine Schnittstelle sagt T const&, aber es ist in der Tat ein int&. Nicht wirklich expressiver Code, oder?

So machen Sie die const-Referenz zu einer Referenz auf const

Wie können wir das Problem beheben, nachdem wir den ersten Schritt zum Verständnis des Problems hinter uns haben? Das heißt, wie können wir get() einen Verweis auf den gespeicherten Wert zurückgeben, der es nicht erlaubt, ihn zu ändern?

Eine Möglichkeit, dies zu tun, besteht darin, explizit ein const in die Referenz einzufügen. Wir können dies tun, indem wir die Referenz entfernen, eine const hinzufügen und die Referenz zurücksetzen. Um die Referenz zu entfernen, verwenden wir std::remove_reference in C ++ 11 oder das bequemere std::remove_reference_t in C++14:

Wie Sie sehen können, wenn Sie auf Ausführen klicken, wird jetzt der erwartete Kompilierungsfehler angezeigt, wenn Sie versuchen, den in Wrapper gespeicherten Wert zu ändern.

Ich denke, es ist nützlich zu verstehen, was mit dieser Referenz los ist, da dies Code ist, der sicher aussieht, aber wirklich nicht, und dies ist die gefährlichste Art.

Sie können mit dem Code in den obigen Spielplätzen herumspielen, die in die Seite eingebettet sind. Und ein großes Dankeschön an Stack Overflow-Benutzer rakete1111, der mir geholfen hat, die Gründe für dieses Problem zu verstehen.

Das könnte Ihnen auch gefallen

  • Die ärgerlichste Analyse: Wie man sie erkennt und schnell repariert

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.