Jakie są konsekwencje gwarancji kolejności oceny głosowanych w C ++ 17 (P0145) w typowym kodzie C ++?
Co to zmienia w następujących sytuacjach?
i = 1;
f(i++, i)
i
std::cout << f() << f() << f();
lub
f(g(), h(), j());
c++
c++17
operator-precedence
Johan Lundberg
źródło
źródło
Odpowiedzi:
Niektóre typowe przypadki, w których kolejność oceny do tej pory nie została określona , są określone i ważne z
C++17
. Niektóre niezdefiniowane zachowania są teraz nieokreślone.był nieokreślony, ale teraz jest nieokreślony. W szczególności nie określono kolejności, w jakiej każdy argument
f
jest oceniany względem innych.i++
można ocenić wcześnieji
lub odwrotnie. Rzeczywiście, może ocenić drugie wywołanie w innej kolejności, mimo że znajduje się w tym samym kompilatorze.Jednak ocena każdego argumentu musi zostać wykonana w całości, ze wszystkimi skutkami ubocznymi, przed wykonaniem jakiegokolwiek innego argumentu. Więc możesz otrzymać
f(1, 1)
(drugi argument jest oceniany jako pierwszy) lubf(1, 2)
(pierwszy argument jest oceniany jako pierwszy). Ale nigdy nie dostanieszf(2, 2)
ani niczego podobnego.nie została określona, ale stanie się zgodna z priorytetem operatorów, tak że pierwsza ocena
f
będzie miała miejsce jako pierwsza w strumieniu (przykłady poniżej).nadal ma nieokreśloną kolejność ocen g, h i j. Zauważ, że dla
getf()(g(),h(),j())
, reguły określają, żegetf()
będą oceniane wcześniejg, h, j
.Zwróć również uwagę na następujący przykład z tekstu oferty:
Przykład pochodzi z The C ++ Programming Language , 4th edition, Stroustrup i było wcześniej nieokreślonym zachowaniem, ale z C ++ 17 będzie działać zgodnie z oczekiwaniami. Podobne problemy występowały w przypadku funkcji wznawiania (
.then( . . . )
).Jako inny przykład rozważ następujące kwestie:
#include <iostream> #include <string> #include <vector> #include <cassert> struct Speaker{ int i =0; Speaker(std::vector<std::string> words) :words(words) {} std::vector<std::string> words; std::string operator()(){ assert(words.size()>0); if(i==words.size()) i=0; // Pre-C++17 version: auto word = words[i] + (i+1==words.size()?"\n":","); ++i; return word; // Still not possible with C++17: // return words[i++] + (i==words.size()?"\n":","); } }; int main() { auto spk = Speaker{{"All", "Work", "and", "no", "play"}}; std::cout << spk() << spk() << spk() << spk() << spk() ; }
Z C ++ 14 i zanim będziemy mogli (i będziemy) uzyskiwać wyniki takie jak
play no,and,Work,All,
zamiast
All,work,and,no,play
Zauważ, że powyższe działa tak samo jak
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
Ale nadal przed C ++ 17 nie było gwarancji, że pierwsze wywołania pojawią się jako pierwsze w strumieniu.
Referencje: Z zaakceptowanej propozycji :
Uwaga dotycząca edycji: moja pierwotna odpowiedź została błędnie zinterpretowana
a(b1, b2, b3)
. Kolejnośćb1
,b2
,b3
nadal jest nieokreślony. (dziękuję @KABoissonneault, wszystkim komentującym.)Jednak (jak @Yakk zaznacza) i to jest ważne: Nawet jeśli
b1
,b2
,b3
są wyrażeniami nietrywialne, każda z nich są całkowicie ocenione i przywiązany do odpowiedniego parametru funkcji zanim inni są te zaczęły być oceniane. Norma mówi tak:Jednak jednego z tych nowych zdań brakuje w wersji roboczej GitHub :
Przykładem jest tam. Rozwiązuje problemy sprzed dziesięcioleci ( jak wyjaśnił Herb Sutter ) z wyjątkiem bezpieczeństwa
f(std::unique_ptr<A> a, std::unique_ptr<B> b); f(get_raw_a(), get_raw_a());
przeciekałby, gdyby jedno z wywołań
get_raw_a()
zgłosiło się, zanim drugi surowy wskaźnik zostałby powiązany z jego parametrem inteligentnego wskaźnika.Jak wskazał TC, przykład jest błędny, ponieważ konstrukcja unique_ptr z surowego wskaźnika jest jawna, co zapobiega kompilacji. *
Zwróć również uwagę na to klasyczne pytanie (oznaczone jako C , a nie C ++ ):
jest nadal nieokreślona.
źródło
a
, potemb
,c
potemd
”, a następnie pokazujęa(b1, b2, b3)
, sugerując, że wszystkieb
wyrażenia niekoniecznie są oceniane w dowolnej kolejności (w przeciwnym razie byłobya(b, c, d)
)a(b1()(), b2()())
Można zamówićb1()()
ib2()()
w dowolnej kolejności, ale nie może zrobićb1()
wtedyb2()()
wtedyb1()()
: to już nie może przeplot ich egzekucji. Krótko mówiąc, „8. ALTERNATYWNA KOLEJNOŚĆ OCENY POŁĄCZEŃ FUNKCYJNYCH” była częścią zatwierdzonej zmiany.f(i++, i)
był niezdefiniowany. Jest teraz nieokreślony. Przykład łańcucha Stroustrupa był prawdopodobnie nieokreślony, a nie nieokreślony. `f (get_raw_a (), get_raw_a ());` nie skompiluje się, ponieważ odpowiedniunique_ptr
konstruktor jest jawny. Wreszciex++ + ++x
jest niezdefiniowana, kropka.Przeplot jest zabroniony w C ++ 17
W C ++ 14 następujące elementy były niebezpieczne:
void foo(std::unique_ptr<A>, std::unique_ptr<B>); foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
Podczas wywołania funkcji mają miejsce cztery operacje
new A
unique_ptr<A>
konstruktornew B
unique_ptr<B>
konstruktorIch kolejność była całkowicie nieokreślona, a więc doskonale poprawna kolejność to (1), (3), (2), (4). Gdyby ta kolejność została wybrana i (3) zrzuca, to pamięć z (1) wycieka - jeszcze nie uruchomiliśmy (2), co by zapobiec wyciekowi.
W C ++ 17 nowe reguły zabraniają przeplotu. Z [intro.execution]:
Jest przypis do tego zdania, który brzmi:
To daje nam dwa prawidłowe uporządkowania: (1), (2), (3), (4) lub (3), (4), (1), (2). Nie jest określone, która kolejność jest pobierana, ale obie opcje są bezpieczne. Wszystkie porządki, w których (1) (3) występują zarówno przed (2), jak i (4), są teraz zabronione.
źródło
Znalazłem kilka uwag na temat kolejności oceny wyrażeń:
W P0145R3.Refining Expression Evaluation Order for Idiomatic C ++ znalazłem:
Ale nie znalazłem tego w standardzie, zamiast tego w standardzie znalazłem:
Więc porównałem zachowanie trzech kompilatorów dla standardów 14 i 17. Eksplorowany kod to:
#include <iostream> struct A { A& addInt(int i) { std::cout << "add int: " << i << "\n"; return *this; } A& addFloat(float i) { std::cout << "add float: " << i << "\n"; return *this; } }; int computeInt() { std::cout << "compute int\n"; return 0; } float computeFloat() { std::cout << "compute float\n"; return 1.0f; } void compute(float, int) { std::cout << "compute\n"; } int main() { A a; a.addFloat(computeFloat()).addInt(computeInt()); std::cout << "Function call:\n"; compute(computeFloat(), computeInt()); }
Wyniki (tym bardziej spójne jest clang):
<style type="text/css"> .tg { border-collapse: collapse; border-spacing: 0; border-color: #aaa; } .tg td { font-family: Arial, sans-serif; font-size: 14px; padding: 10px 5px; border-style: solid; border-width: 1px; overflow: hidden; word-break: normal; border-color: #aaa; color: #333; background-color: #fff; } .tg th { font-family: Arial, sans-serif; font-size: 14px; font-weight: normal; padding: 10px 5px; border-style: solid; border-width: 1px; overflow: hidden; word-break: normal; border-color: #aaa; color: #fff; background-color: #f38630; } .tg .tg-0pky { border-color: inherit; text-align: left; vertical-align: top } .tg .tg-fymr { font-weight: bold; border-color: inherit; text-align: left; vertical-align: top } </style> <table class="tg"> <tr> <th class="tg-0pky"></th> <th class="tg-fymr">C++14</th> <th class="tg-fymr">C++17</th> </tr> <tr> <td class="tg-fymr"><br>gcc 9.0.1<br></td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> </tr> <tr> <td class="tg-fymr">clang 9</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td> </tr> <tr> <td class="tg-fymr">msvs 2017</td> <td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td> </tr> </table>
źródło