2 Stimmen

Wenn die Methode den Parameter nicht ändert, warum kann ich ihn dann nicht als const übergeben?

Ich bin ein Student, der const-Parameter immer noch nicht ganz versteht. Ich verstehe, warum dies nicht funktioniert:

#include 

struct Node {

    int Wert;
    Node* nächste;
};

Node* cons(const int Wert, const Node * nächste) {

    Node * tmp = new Node();
    tmp->Wert = Wert;
    tmp->nächste = nächste;

    return tmp;
}

int main () {

    const Node * k;
    k = cons(1, NULL);

    Node * p;

    p = cons(2, k);

}

Das liegt daran, dass ich die Konstanz von k "abwürfe", indem ich p seine Adresse gebe.

Was ich durch die Markierung des Parameters als "const" beabsichtigt habe, war zu sagen, dass meine Methode den Knoten nicht direkt ändern wird. Wenn ich (Node * nächste) übergeben würde, würde ich das Gefühl haben, dass es keine Garantie gibt, dass meine Methode nicht auf diesen Zeiger zugreift und den Knoten durcheinander bringt. Vielleicht ist das töricht, da es dann keine Garantie gibt, dass der const Node, dem ich einen Zeiger übergeben habe, später über den neuen Zeiger darauf geändert wird. Es scheint nur seltsam, dass: in dieser Methode cons, alles was ich tue, ist, einen neuen Zeiger auf 'nächste' zu zeigen - ich habe 'nächste' nie berührt, nur darauf gezeigt - und doch reicht das aus, um die Konstanz zu brechen.

Vielleicht habe ich die Stärke des "const" -Versprechens einfach unterschätzt.

Danke!

9voto

Frerich Raabe Punkte 85859

Das Problem ist, dass Ihr next-Argument vom Typ const Node * ist (das heißt: ein Zeiger auf ein konstantes Node-Objekt), während das Node::next-Feld vom Typ Node * ist (ein Zeiger auf ein nicht-const Node-Objekt).

Also, wenn Sie das Folgende tun

tmp->next = next;

Versuchen Sie, den Compiler einen Zeiger auf ein konstantes Node-Objekt nehmen zu lassen und es einem Zeiger auf ein nicht-const Node-Objekt zuzuweisen. Wenn dies funktionieren würde, wäre es eine einfache Möglichkeit, die Konstanz zu umgehen, denn plötzlich könnten Leute einfach das next-Argument ändern, indem sie tmp->next dereferenzieren.

3voto

Vielleicht habe ich die Stärke des "const" Versprechens einfach unterschätzt.

Also, was ist das Versprechen, das du gemacht hast? Die Funktion nimmt eine Kopie eines Zeigers auf ein Node und verspricht, dass keine Modifikation dieses Node erlaubt wird. Das Versprechen bezieht sich nicht auf den Zeiger selbst, das wäre Node * const (und ziemlich nutzlos, da er per Wert übergeben wird und das const auf dieser Ebene aus der Signatur entfernt wird), sondern auf den Inhalt, den Node.

Um ein Beispiel aus dem echten Leben zu geben, stelle dir vor, ein Freund hat angeboten, deine Pflanzen zu gießen, während du im Urlaub bist, unter dem Versprechen (normalerweise implizit), dass er nichts aus deinem Haus stehlen wird. Nun machst du eine Kopie des Schlüssels und gibst sie deinem Freund, der wiederum eine Kopie des Schlüssels macht und sie einer dritten Person übergibt, die niemals versprochen hat, dich nicht auszurauben. Würdest du sagen, dass dein Freund sein Versprechen gebrochen hat? Wie stark war sein Versprechen?

Der obige Code hat genau dasselbe Muster, cons verspricht, dass du ihm eine Kopie eines Zeigers auf ein Node geben kannst und dass er es nicht erlauben wird, dass es verändert wird, aber sobald du den Code betrittst, kopiert er diesen Zeiger zu einem anderen Zeiger, der keinerlei Versprechen über den Node macht, und gibt dann die Kopie an jeden weiter, der daran interessiert sein könnte.

2voto

Kerrek SB Punkte 445528

Wenn T ein beliebiger Typ ist, gibt es zwei verwandte Pointer-Typen, T * und const T *.

Sie erhalten Pointer in der Natur, indem Sie die "Adresse" eines Objekts vom Typ T nehmen. Der Typ des Pointers hängt von der Konstanz des Objekts ab:

T x;
const T y;

T * p1       = &x; // OK, &x ist T*
// T * p2    = &y; // Error!
const T * q1 = &y; // OK, &y ist const T *
const T * q2 = &x; // auch in Ordnung

Wenn wir also ein veränderliches Objekt haben, ist ein Zeiger darauf natürlich ein Zeiger auf Veränderliches. Wenn wir jedoch nur ein konstantes Objekt haben, kann ein Zeiger nur ein Zeiger auf Konstantes sein. Da wir ein veränderliches Objekt immer als ein konstantes behandeln können (einfach nicht berühren), kann ein Zeiger auf Veränderliches immer in einen Zeiger auf Konstantes umgewandelt werden, wie im Fall von q2.

Das ist es, was mit deinem k passiert: Es ist ein const T * (mit T = Node), und wir weisen ihm den Wert eines T * zu, was in Ordnung ist, da wir einfach ignorieren, dass wir T mutieren könnten, wenn wir wollten.

[Im echten Leben haben wir normalerweise konstante Referenzen anstelle von absolut konstanten Objekten, aber du kannst diese im Wesentlichen als dasselbe behandeln (da eine Referenz nur ein Alias für ein Objekt ist und funktional identisch mit dem Objekt selbst ist).]

Jetzt könntest du all diese consts mit der Konstanz des Objekts selbst durcheinander bringen: Genau wie wir zuvor x und y hatten, eins veränderlich und eins konstant, können wir dieselbe Logik auf den Pointer-Typ selbst anwenden: Lassen Sie uns P = T * und Q = const T * definieren. Dann war in obigem Code all unsere Objekte veränderliche Objekte P p1, p2; Q q1, q2;. Das heißt, wir können alle Zeiger immer irgendwohin zeigen lassen: q2 = &z;. Kein Problem. Das ist es, was du mit k machst, wenn du es neu zuweist.

Natürlich können wir auch konstante Versionen dieser Zeiger deklarieren: const P p3 = &x; const Q q3 = &y;. Diese können nicht neu zugewiesen werden (da sie schließlich Konstanten sind). Wenn Sie das in Bezug auf T ausschreiben, wie es die Leute normalerweise tun, wird es etwas lauter:

T       * p3 const = &x;
const T * q3 const = &y;

Ich hoffe, das hat nicht mehr Verwirrung als Klarheit geschaffen!

1voto

Max Lybbert Punkte 19181

Vielleicht ist das albern, da dann keine Garantie besteht, dass der spätere const Node, auf den ich einen Zeiger übergeben habe, über den neuen Zeiger zu ändern ist. Es scheint nur seltsam, dass: in dieser Methode cons, alles, was ich tue, ist einen neuen Zeiger auf 'nächster' zu zeigen - ich habe 'next' nie berührt, nur gezeigt - und doch reicht das aus, um die Konstanz zu brechen.

Es ist nicht einfach so, dass du gezeigt hast, sondern dass du mit einem nicht-const-Zeiger gezeigt hast. Wenn du Node::entry zu einem const Node* machst, sollte es funktionieren.

Natürlich beschränkt das, was du später bei der Manipulation deiner Liste tun kannst.

Es gibt effektiv zwei verschiedene Formen von const in C++: "wirklich-ehrliche const" und "const soweit du jetzt betroffen bist." Die meiste Zeit wirst du es mit der zweiten Art zu tun haben, wie im Beispiel. Dein Programm muss jedoch korrekt mit beiden Formen von const umgehen, um konstant korrekt zu sein:

int main()
{
    const Node n = { 5, NULL };
    Node* p = cons(6, &n);
}

Es ist undefiniertes Verhalten, das const auf n (oder auf einen Zeiger auf n) wegzuwerfen, und cons muss das respektieren. Ich sollte auch erwähnen, dass ich glaube, dass ein Teil deiner Annahme darin besteht, dass du erkennst, dass deine Funktion möglicherweise das const Node* ändern könnte, aber argumentierst, dass, da die problematische Zuweisung die letzte Zeile der Methode ist, der Compiler erkennen sollte, dass es tatsächlich nichts falsch macht. Mir ist keine Regel in C++ bekannt, die besagt "es ist illegal, X zu tun, es sei denn, natürlich, du machst es als letzte Zeile einer Funktion." Die Zuweisung ist unabhängig davon, ob sie als erste oder letzte in der Funktion steht, nicht erlaubt.

Mit etwas Glück kannst du das gewünschte Design erhalten und trotzdem die const-Korrektheit haben. Zum Beispiel bist du berechtigt, cons zu überladen, um entweder ein const Node* oder ein Node* anzunehmen und entsprechend Listen von const oder nicht-const Elementen aufzubauen. Wenn ich mehr darüber wüsste, was du tun möchtest, hätte ich vielleicht eine bessere Antwort, wie du dorthin gelangen kannst.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X