13

Ich bin verwirrt mit einer Frage, die ich in einem C++ Test sah. Der Code ist hier:Verständnis eines überladenen Operators [] Beispiel

#include <iostream> 
using namespace std; 

class Int { 
public: 
    int v; 
    Int(int a) { v = a; } 
    Int &operator[](int x) { 
     v+=x; 
     return *this; 
    } 
}; 
ostream &operator<< (ostream &o, Int &a) { 
    return o << a.v; 
} 

int main() { 
    Int i = 2; 
    cout << i[0] << i[2]; //why does it print 44 ? 
    return 0; 
} 

ich irgendwie sicher war, dass dieses 24 drucken würde, sondern druckt es 44. Ich möchte wirklich, dass jemand das klarstellt. Ist es eine kumulative Bewertung? Ist auch die <<binäre Infix?

Vielen Dank im Voraus

EDIT: bei nicht gut Betreiber Überlastung definiert, könnte jemand eine bessere Umsetzung der überladenen Operatoren hier geben, damit es 24 drucken würde?

+1

'cout << i [0] << i [2];' könnte auch so geschrieben werden: 'operator << (operator << (cout, i [0]), i [2]);' – Xaqq

+1

scheint die Reihenfolge der Auswertung von zwei << nicht gut definiert. Debuggen Sie Ihren Code und Sie werden sehen, dass das i [2] zuerst aufgerufen wird. – Tim3880

+0

Ich würde wirklich vorschlagen, dass 'operator []' Nebenwirkungen haben. Ich denke, dieses Verhalten wird dich irgendwann wieder beißen und nicht nur hier. Stellen Sie sich vor, wie das mit Multithreading interagiert, z.B. –

Antwort

16

Dieses Programm unbestimmtes Verhalten hat: der Compiler nicht i[0] und i[2] in einem von links nach rechts, um erforderlich ist, um zu bewerten (die Sprache C++ gibt diese Freiheit zu Compiler, um Optimierungen zu ermöglichen).

Zum Beispiel Clang does it in this instance, während GCC does not.

Die Reihenfolge der Auswertung ist nicht festgelegt. Sie können also keine konsistente Ausgabe erwarten, selbst wenn Sie Ihr Programm wiederholt auf einer bestimmten Maschine ausführen.

Wenn Sie konsistente Ausgabe erhalten möchten, können Sie die oben umformulieren wie folgt (oder in äquivalenter Weise):

cout << i[0]; 
cout << i[2]; 

Wie Sie sehen können, GCC no longer outputs 44 in diesem Fall.

EDIT:

Wenn aus welchem ​​Grund auch immer, die Sie wirklich wirklich den Ausdruck einfügen möchten cout << i[0] << i[2]24 drucken, erhalten Sie die Definition der überladenen Operatoren (operator [] und operator <<) erheblich ändern müssen, denn die Sprache absichtlich macht es unmöglich zu sagen, welcher Teilausdruck (i[0] oder [i2]) zuerst ausgewertet wird.

Die einzige Garantie, die Sie hier bekommen, ist, dass das Ergebnis i[0] der Auswertung in cout, bevor das Ergebnis eingefügt werden soll i[2] auszuwerten, um so die beste Wahl wahrscheinlich ist operator << führen die Modifikation von Int's Daten Mitglied v, lassen eher als operator [].

jedoch das Delta, den v angewandt werden soll, wird als Argument an operator [] vergangen, und Sie brauchen einen Weg, es zu operator << zusammen mit dem ursprünglichen Int Objekt weitergeleitet werden.Eine Möglichkeit ist operator [] Rückkehr eine Datenstruktur zu lassen, der das Delta sowie ein Verweis auf das ursprüngliche Objekt enthält:

class Int; 

struct Decorator { 
    Decorator(Int& i, int v) : i{i}, v{v} { } 
    Int& i; 
    int v; 
}; 

class Int { 
public: 
    int v; 
    Int(int a) { v = a; } 
    Decorator operator[](int x) { 
     return {*this, x}; // This is C++11, equivalent to Decorator(*this, x) 
    } 
}; 

Jetzt brauchen Sie nur operator << so zu umschreiben, dass es akzeptiert eine Decorator Objekt, ändert das referenzierte Int Objekt durch das gespeicherte delta zum v Datenelement Anlegen druckt dann seinen Wert:

ostream &operator<< (ostream &o, Decorator const& d) { 
    d.i.v += d.v; 
    o << d.i.v; 
    return o; 
} 

Hier ist ein live example.

Haftungsausschluss: Wie andere erwähnt haben, bedenken Sie, dass operator [] und operator << sind in der Regel nicht mutiert Operationen (genauer gesagt, sie nicht mutieren, den Zustand des Objekts, die indiziert ist und Strom eingefügt, respectively), Sie werden also davon abgehalten, Code wie diesen zu schreiben, es sei denn, Sie versuchen nur, einige C++ - Quizfragen zu lösen.

+4

@vsoftco: Das Verhalten ist in diesem Fall nicht undefiniert. Aufrufe zu überladenen 'operator []' sind nur normale Funktionsaufrufe und können nicht verschachtelt werden (siehe Abschnitt 1.9/15) –

+1

Hmm, aber der Par. endet mit: * wenn ein Nebeneffekt auf ein Skalarobjekt relativ zu einem anderen Nebeneffekt desselben Skalarobjekts oder einer Wertberechnung unter Verwendung des Werts desselben Skalarobjekts nicht sequenziert wird und das Verhalten nicht gleichzeitig (1.10) ist ist undefiniert. * Ich bin ein bisschen verwirrt, ist nicht der 'Operator []' ein "Nebeneffekt"? Ohh ok ok, ich habe den Teil nach dem Beispiel verpasst. Vielen Dank! – vsoftco

+1

@vsoftco: Richtig, Fußnote 9 verdeutlicht auch ein wenig: "* Mit anderen Worten, Funktionsausführungen verschachteln sich nicht miteinander *." –

5

Um zu erklären, was hier vor sich geht, lassen Sie uns die Dinge viel einfacher machen: cout<<2*2+1*1;. Was passiert zuerst, 2 * 2 oder 1 * 1? Eine mögliche Antwort ist 2 * 2 sollte zuerst passieren, da es die am weitesten links ist. Aber der C++ - Standard sagt: Wen interessiert's ?! Immerhin ist das Ergebnis 5 oder so. Aber manchmal ist es wichtig. Zum Beispiel, wenn f und g sind zwei Funktionen, und wir tun f()+g(), dann gibt es keine Garantie, die zuerst aufgerufen wird. Wenn f eine Nachricht ausgibt, aber g das Programm beendet, wird die Nachricht möglicherweise nie gedruckt. In Ihrem Fall wurde i[2] vor i[0] aufgerufen, weil C++ denkt, dass es keine Rolle spielt. Sie haben zwei Möglichkeiten:

Eine Möglichkeit besteht darin, Ihren Code zu ändern, damit es keine Rolle spielt. Schreiben Sie Ihren []-Operator so um, dass er Int nicht ändert, und gibt stattdessen einen neuen Int zurück. Dies ist wahrscheinlich eine gute Idee, da dies es mit 99% aller anderen [] Betreiber auf dem Planeten vereinbar macht. Es erfordert auch weniger Code:

Int &operator[](int x) { return this->v + x;}.

Die andere Option ist Ihr [] das gleiche zu halten, und teilen Sie Ihre Druck in zwei Anweisungen:

cout<<i[0]; cout<<i[2];

Einige Sprachen tatsächlich das tun garantieren in 2*2+1*1, die 2 * 2 zuerst erfolgt. Aber nicht C++.

Edit: Ich war nicht so klar wie ich gehofft hatte. Lass es uns langsamer versuchen. C++ kann auf zwei Arten ausgewertet werden: 2*2+1*1.

Methode 1: 2*2+1*1 ---> 4+1*1 ---> 4+1 --->5.

Methode 2: 2*2+1*1 ---> 2*2+1 ---> 4+1 --->5.

In beiden Fällen erhalten wir die gleiche Antwort.

Versuchen wir das nochmal mit einem anderen Ausdruck: i[0]+i[2].

Methode 1: i[0]+i[2] ---> 2+i[2] ---> 2+4 ---> 6.

Methode 2: i[0]+i[2] ---> i[0]+4 ---> 4+4 ---> 8.

Wir haben eine andere Antwort, weil die [] Nebenwirkungen hat, so ist es wichtig, ob wir zuerst i[0] oder i[2] tun. Laut C++ sind dies beide gültige Antworten. Jetzt sind wir bereit, Ihr ursprüngliches Problem anzugreifen. Wie Sie bald sehen werden, hat es mit dem Operator << fast nichts zu tun.

Wie geht C++ mit cout << i[0] << i[2]? Wie zuvor gibt es zwei Möglichkeiten.

Methode 1: cout << i[0] << i[2] ---> cout << 2 << i[2] ---> cout << 2 << 4.

Methode 2: cout << i[0] << i[2] ---> cout << i[0] << 4 ---> cout << 4 << 4.

Die erste Methode wird 24 wie erwartet gedruckt. Aber nach C++ ist Methode 2 genauso gut und es wird 44 gedruckt, wie Sie es gesehen haben. Beachten Sie, dass das Problem auftritt, bevor << aufgerufen wird. Es gibt keine Möglichkeit, << zu überlasten, um dies zu verhindern, denn bis zum Zeitpunkt << ist der "Schaden" bereits aufgetreten.

+0

+1 für die Erwähnung, wie extrem dumm die [] Methode hier ist (und andere Sachen, die auch erwähnt werden!) Weil ... Ich habe diesen Gebrauch noch nie zuvor gesehen! –

+0

@MarkVY Hallo Markus. Wo hast du gelesen, dass C++ in Operatoren keine Priorität hat? '*' Operator hat höhere Priorität als '+'. In jedem Fall liegt das Problem in diesem Codebeispiel nicht im tiefgestellten Operator '[]', sondern in '' ''. – BugShotGG

+0

@GeoPapas Ich denke, du hast diese Antwort falsch gelesen. C++ hat Priorität in Operatoren und '*' hat höhere Priorität als '+', diese Antwort geht davon aus, dass Sie das bereits wissen. Vielleicht mischen Sie Priorität mit der Reihenfolge der Auswertung. –

Verwandte Themen