2017-08-28 6 views
1

Wenn Sie einen Iterator schreiben wollen, schreiben Sie in der RegelVerständnis const

T* operator->() const; 

Mein Problem ist das Verständnis dieses „const“ mit Zeigern und Referenzen.

Zum Beispiel können Sie die folgende Struktur schreiben:

struct A{ 
    int* x; 

    A(int& x0):x{&x0}{} 
    int* ptr() const {return x;} 
    int& ref() const {return *x;} 
}; 

Und Sie können es auf diese Weise verwenden:

int x = 10; 
const A a{x}; 

int& r = a.ref(); 
int* p = a.ptr(); 

*p = 4; 
cout << x << endl; // prints 4 

r = 25; 
cout << x << endl; // prints 25 

Aber warum diese kompiliert und funktioniert richtig (zumindest mit g ++ und Klirren). Warum?

Wie ich definiert

const A a{x}; 

dieses "a" ist konst. Also, wenn ich

nennen
int* p = a.ptr(); 

Ich rufe ptr() mit einem konstanten Objekt, so dass der interne Zeiger A-> x muss "int * const" sein. Aber ich gebe ein "int *" ohne const zurück. Warum ist das korrekt?

Und was passiert mit der Referenz? Wenn ich A :: ref() mit einem "const A" aufrufe, welchen Typ gibt diese Funktion zurück? So etwas wie "int & const" ??? < --- Ich nehme an, das ist das gleiche wie "int &".

Danke für Ihre Hilfe.

+3

Sie können jederzeit eine konstante Sache kopieren. – molbdnilo

Antwort

10

Es gibt einen Unterschied zwischen bitweisem const und logischem const. Wenn Sie einen const A haben, können Sie seinen int* Member nicht ändern. Aber es gibt einen Unterschied zwischen dem Modifizieren des Elements selbst (welchesint, das x verweist auf) und dem Modifizieren durch das Element (der Wert des int dass x verweist auf). Das sind verschiedene Arten von const. Im einfachsten Fall:

struct Ptr { int* p; }; 

int i = 42; 
const Ptr ptr{&i}; 
*ptr.p = 57; 

ptr.p noch deutet auf i, änderte nichts da, so dass die const Mechaniker erzwungen wird. Aber es ist nicht logisch const, da Sie noch etwas durch ein const Objekt geändert haben. Die Sprache erzwingt das jedoch nicht für Sie. Das liegt an Ihnen als Bibliotheksautor.

Wenn Sie wollenconst -ness propagieren, müssen Sie nur eine Schnittstelle bereitstellen, die logisch ist const:

int const* ptr() const {return x;} 
int const& ref() const {return *x;} 
// ^^^^^ 

Jetzt können Benutzer Ihre const A sowohl bitweise nicht ändern durch (kann nicht ändern, was x zeigt auf) und logisch (kann den Wert dieses Pointees auch nicht ändern).

2

In struct A, wenn Sie eine Instanz davon const machen, machen Sie den Zeiger konstant, aber das macht nicht automatisch die spitze zu konstanten Objekt.

Deklarieren so etwas wie const A a(ref); im Grunde entspricht den folgenden Code zum Aufruf:

struct A_const { 
    int * const x; 

    A(int& x0):x{&x0}{} 
    int* ptr() const {return x;} 
    int& ref() const {return *x;} 
}; 

Wenn Sie Ihre Zeiger Regeln erinnern, bedeutet dies, dass x ein konstanter Zeiger ist, das heißt, es kann nicht auf etwas hinweisen, gemacht werden sonst (es zu einem Bezug funktionell ähnlich ist, kann aber null sein), aber kritisch, die int, dass es nicht konstant ist, zeigt, was bedeutet, dass Sie nichts hört von so etwas wie diese schreiben:

int val = 17; 
const A a(val); 
*(a.val) = 19; //Totally valid, compiles, and behaves as expected! 
int val2 = 13; 
//a.val = &val2; //Invalid, will invoke compile-time error 

Dies ist auch der Grund, warum std::unique_ptr<int> und std::unique_ptr<const int> verschiedene Objekte darstellen.

Wenn das Objekt, auf das verwiesen wird, nicht auf einem Objekt const geändert werden kann, müssen Sie dies im Code selbst erzwingen. Da Funktionen können auf der Grundlage der überlastet werden, ob das Quellobjekt const oder nicht, das ist ziemlich einfach:

struct A { 
    int * x; 

    A(int& x0):x{&x0}{} 
    int * ptr() {return x;} 
    int & ref() {return *x;} 
    int const* ptr() const {return x;} 
    int const& ref() const {return *x;} 
}; 

int val = 17; 
A a(val); 
a.ref() = 19;//Okay. 
*a.ptr() = 4;//Okay. 

const A b(val); 
b.ref() = 13;//Compile error 
*b.ptr() = 17;//Compile error 
3

Aber warum diese kompiliert und funktioniert richtig (zumindest mit g ++ und Klappern). Warum?

Weil das Programm wohlgeformt ist und Verhalten definiert hat. Die Korrektheit wurde nicht verletzt.

Ich rufe ptr() mit einem const-Objekt auf, also muss der interne Zeiger A-> x "int * const" sein. Aber ich gebe ein "int *" ohne const zurück. Warum ist das korrekt?

Weil es völlig in Ordnung ist, Kopien von const Objekten zu erstellen. Diese Kopien müssen nicht konstant sein. Kopieren eines Objekts nicht Änderungen an dem ursprünglichen Objekt machen (vorausgesetzt, kein Benutzer ist Copykonstruktor definiert, die dumme Dinge tut).

Und was passiert mit der Referenz? Wenn ich A :: ref() mit einem "const A" aufrufe, welchen Typ gibt diese Funktion zurück?

int& ref() gibt immer int& zurück. Genau wie int* ptr() immer int* zurückgibt.

Etwas wie "int & const" ???

Es gibt keine solche Sache wie int& const. Referenzen können keine Qualifikationsmerkmale auf oberster Ebene haben (sie können niemals neu zugewiesen werden).

+1

Wo wird eine Kopie erstellt? Sowohl 'ref()' als auch 'ptr()' geben eine nicht konstante Referenz oder einen Zeiger auf eine Elementvariable zurück. Da das Objekt const ist, kann das Ändern seines Members auf diese Weise zu undefiniertem Verhalten führen. –

+1

@MarkRansom Weder gibt eine Referenz oder einen Zeiger auf die Elementvariablen. 'Ptr' gibt eine Kopie * * ein Mitglied Variable (das ist, wo ich eine Kopie zu sehen, eine weitere Kopie ist in der Initialisierung von' P' mit dem Rückgabewert aus dieser Funktion) und 'ref' eine Referenz auf das Objekt zurückzugibt hingewiesen durch die Mitgliedsvariable. Sie können das Element mit diesen Funktionen nicht ändern.Ich sehe kein UB im Code in der Frage. – user2079303

+0

OK, ich verstehe Ihren Standpunkt - es war ein Leseverständnisproblem an meinem Ende. Ich war verwirrt über die Anzahl der Ebenen der Indirektion. Da 'int x = 10 '* nicht * const ist und das Objekt einen Zeiger auf diese externe Variable hält, gibt es kein undefiniertes Verhalten. –