In Bjarne Stroustrups The C ++ Programming Language 4. Ausgabe, Abschnitt 36.3.6
STL-ähnliche Operationen, wird der folgende Code als Beispiel für die Verkettung verwendet :
void f2()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
.replace( s.find( " don't" ), 6, "" );
assert( s == "I have heard it works only if you believe in it" ) ;
}
Die Zusicherung schlägt in gcc
( live sehen ) und Visual Studio
( live sehen ) fehl, aber sie schlägt nicht fehl, wenn Clang verwendet wird ( live sehen ).
Warum bekomme ich unterschiedliche Ergebnisse? Bewerten einige dieser Compiler den Verkettungsausdruck falsch oder weist dieser Code irgendeine Form von nicht spezifiziertem oder undefiniertem Verhalten auf ?
c++
c++11
language-lawyer
operator-precedence
unspecified-behavior
Shafik Yaghmour
quelle
quelle
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );
cout << a << b << c
≡operator<<(operator<<(operator<<(cout, a), b), c)
nur unwesentlich weniger hässlich ist.Antworten:
Der Code zeigt aufgrund der nicht spezifizierten Reihenfolge der Auswertung von Unterausdrücken ein nicht spezifiziertes Verhalten, obwohl er kein undefiniertes Verhalten hervorruft, da alle Nebenwirkungen innerhalb von Funktionen ausgeführt werden, was in diesem Fall eine Sequenzierungsbeziehung zwischen den Nebenwirkungen einführt .
Dieses Beispiel wird im Vorschlag N4228 erwähnt: Verfeinern der Ausdrucksauswertungsreihenfolge für idiomatisches C ++ , in dem Folgendes zum Code in der Frage angegeben wird:
Einzelheiten
Für viele mag es offensichtlich sein, dass Argumente für Funktionen eine nicht spezifizierte Bewertungsreihenfolge haben, aber es ist wahrscheinlich nicht so offensichtlich, wie dieses Verhalten mit verketteten Funktionsaufrufen interagiert. Es war mir nicht klar, als ich diesen Fall zum ersten Mal analysierte, und anscheinend auch nicht allen Experten .
Auf den ersten Blick mag es so aussehen,
replace
als müssten die entsprechenden Funktionsargumentgruppen auch als Gruppen von links nach rechts ausgewertet werden , da jede von links nach rechts ausgewertet werden muss.Dies ist falsch, Funktionsargumente haben eine nicht spezifizierte Auswertungsreihenfolge, obwohl das Verketten von Funktionsaufrufen eine Auswertungsreihenfolge von links nach rechts für jeden Funktionsaufruf einführt. Die Argumente jedes Funktionsaufrufs werden nur in Bezug auf den Elementfunktionsaufruf, zu dem sie gehören, vorher sequenziert von. Dies wirkt sich insbesondere auf folgende Aufrufe aus:
und:
die unbestimmt sequenziert sind in Bezug auf:
Die beiden
find
Aufrufe könnten vor oder nach dem ausgewertet werdenreplace
, was wichtig ist, da es eine Nebenwirkung hats
, die das Ergebnis vonfind
verändert und die Länge von änderts
. Je nachdem, wann dies inreplace
Bezug auf die beidenfind
Aufrufe ausgewertet wird, unterscheidet sich das Ergebnis.Wenn wir uns den Verkettungsausdruck ansehen und die Bewertungsreihenfolge einiger Unterausdrücke untersuchen:
und:
Beachten Sie, dass wir die Tatsache ignorieren, dass
4
und7
weiter in weitere Unterausdrücke unterteilt werden können. So:A
wird sequenziert, bevorB
sequenziert wird, bevor vorherC
sequenziert wirdD
1
zu9
werden indeterminately in Bezug auf andere Unterausdrücke mit einigen der Ausnahmen unter sequenziert aufgelistet1
zu3
werden sequenziert vorB
4
zu6
werden sequenziert vorC
7
zu9
werden sequenziert vorD
Der Schlüssel zu diesem Problem ist:
4
in9
Bezug auf unbestimmt sequenziert werdenB
Die mögliche Reihenfolge der Auswahl der Bewertung für
4
und7
in Bezug aufB
erklärt den Unterschied in den Ergebnissen zwischenclang
undgcc
bei der Bewertungf2()
. In meinen Testsclang
auswertet ,B
bevor die Bewertung4
und7
währendgcc
auswertet nach. Wir können das folgende Testprogramm verwenden, um zu demonstrieren, was jeweils passiert:Ergebnis für
gcc
( live sehen )Ergebnis für
clang
( live sehen ):Ergebnis für
Visual Studio
( live sehen ):Details aus dem Standard
Wir wissen , dass die Bewertungen der Unterausdrücke sofern nicht anders angegeben sind unsequenced, diese aus dem ist Entwurf C ++ 11 - Standard Abschnitt
1.9
Programmausführung , die sagt:und wir wissen, dass ein Funktionsaufruf eine vor der Beziehung der Funktionsaufrufe sequenzierte Postfix-Expression und Argumente in Bezug auf den Funktionskörper einführt, aus Abschnitt
1.9
:Wir wissen auch, dass der Zugriff auf Klassenmitglieder und damit die Verkettung von links nach rechts ausgewertet wird, aus dem Abschnitt
5.2.5
Zugriff auf Klassenmitglieder, in dem Folgendes steht:Beachten Sie, dass in dem Fall, in dem der ID-Ausdruck eine nicht statische Elementfunktion ist, die Reihenfolge der Auswertung der Ausdrucksliste innerhalb von nicht angegeben wird,
()
da dies ein separater Unterausdruck ist. Die relevante Grammatik aus5.2
Postfix-Ausdrücken :C ++ 17 Änderungen
Der Vorschlag p0145r3: Verfeinerung der Ausdrucksauswertungsreihenfolge für idiomatisches C ++ hat mehrere Änderungen vorgenommen. Einschließlich Änderungen, die dem Code ein genau festgelegtes Verhalten verleihen, indem die Reihenfolge der Bewertungsregeln für Postfix-Ausdrücke und deren Ausdrucksliste verstärkt wird .
[Ausdruck] p5 sagt:
quelle
"even"
,"don't"
und das mehrere Instanzens
sind unsequenced relativ zueinander.foo().func( bar() )
könnte esfoo()
entweder vor oder nach dem Anruf anrufenbar()
. Der Postfix-Ausdruck istfoo().func
. Die Argumente und der Postfix-Ausdruck werden vor dem Hauptteil von sequenziertfunc()
, jedoch relativ zueinander nicht sequenziert.Dies soll Informationen zu diesem Thema in Bezug auf C ++ 17 hinzufügen. Der Vorschlag ( Refining Expression Evaluation Order für Idiomatic C ++ Revision 2 ) zur
C++17
Behebung des Problems unter Berufung auf den obigen Code war ein Muster.Wie vorgeschlagen, habe ich relevante Informationen aus dem Vorschlag hinzugefügt und zitiert (hebt meine hervor):
Das Papier schlug vor, die Vorregel
C++17
für die Reihenfolge der Expressionsbewertung zu ändern, die vonC
mehr als drei Jahrzehnten beeinflusst wurde und seit mehr als drei Jahrzehnten besteht. Es wurde vorgeschlagen, dass die Sprache zeitgenössische Redewendungen garantiert oder "Fallen und Quellen für dunkle, schwer zu findende Fehler" riskiert , wie dies bei dem obigen Codebeispiel der Fall war.Der Vorschlag für
C++17
ist zu verlangen , dass jeder Ausdruck eine wohldefinierte Auswerteauftrag hat :Der obige Code wird erfolgreich mit
GCC 7.1.1
und kompiliertClang 4.0.0
.quelle