Apa implikasi dari pilihan dalam jaminan urutan evaluasi C ++ 17 (P0145) pada kode C ++ khas?
Apa yang berubah tentang hal-hal seperti berikut ini?
i = 1;
f(i++, i)
dan
std::cout << f() << f() << f();
atau
f(g(), h(), j());
c++
c++17
operator-precedence
Johan Lundberg
sumber
sumber
Jawaban:
Beberapa kasus umum di mana urutan evaluasi sejauh ini belum ditentukan , ditetapkan dan valid dengan
C++17
. Beberapa perilaku tidak terdefinisi kini malah tidak ditentukan.tidak ditentukan, namun sekarang tidak ditentukan. Secara khusus, yang tidak ditentukan adalah urutan di mana setiap argumen
f
dievaluasi relatif terhadap yang lain.i++
mungkin dievaluasi sebelumnyai
, atau sebaliknya. Memang, itu mungkin mengevaluasi panggilan kedua dalam urutan yang berbeda, meskipun berada di bawah compiler yang sama.Namun, evaluasi setiap argumen diperlukan untuk mengeksekusi secara lengkap, dengan semua efek samping, sebelum eksekusi argumen lainnya. Jadi Anda mungkin mendapatkan
f(1, 1)
(argumen kedua dievaluasi terlebih dahulu) atauf(1, 2)
(argumen pertama dievaluasi terlebih dahulu). Tapi Anda tidak akan pernah mendapatkanf(2, 2)
atau apapun dari sifat itu.tidak ditentukan, tetapi akan kompatibel dengan prioritas operator sehingga evaluasi pertama
f
akan didahulukan dalam aliran (contoh di bawah).masih memiliki urutan evaluasi yang tidak ditentukan dari g, h, dan j. Perhatikan bahwa untuk
getf()(g(),h(),j())
, aturan state yanggetf()
akan dievaluasi sebelumnyag, h, j
.Perhatikan juga contoh berikut dari teks proposal:
Contoh ini berasal dari The C ++ Programming Language , edisi ke-4, Stroustrup, dan digunakan untuk perilaku yang tidak ditentukan, tetapi dengan C ++ 17 ini akan berfungsi seperti yang diharapkan. Ada masalah serupa dengan fungsi yang dapat dilanjutkan (
.then( . . . )
).Sebagai contoh lain, pertimbangkan hal berikut:
#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() ; }
Dengan C ++ 14 dan sebelumnya kita mungkin (dan akan) mendapatkan hasil seperti
play no,and,Work,All,
dari pada
All,work,and,no,play
Perhatikan bahwa efek di atas sama seperti
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
Tapi tetap saja, sebelum C ++ 17 tidak ada jaminan bahwa panggilan pertama akan datang lebih dulu ke streaming.
Referensi: Dari proposal yang diterima :
Edit catatan: Jawaban asli saya salah ditafsirkan
a(b1, b2, b3)
. Urutanb1
,b2
,b3
masih tidak ditentukan. (terima kasih @KABoissonault, semua pemberi komentar.)Namun, (sebagai @Yakk menunjukkan) dan ini penting: Bahkan ketika
b1
,b2
,b3
adalah ekspresi non-sepele, masing-masing dari mereka benar-benar dievaluasi dan diikat ke masing-masing parameter fungsi sebelum yang lain mulai dievaluasi. Standar menyatakan ini seperti ini:Namun, salah satu kalimat baru ini hilang dari draf GitHub :
Contoh adalah ada. Ini memecahkan masalah yang sudah berusia puluhan tahun ( seperti yang dijelaskan oleh Herb Sutter ) dengan pengecualian keamanan di mana hal-hal seperti itu
f(std::unique_ptr<A> a, std::unique_ptr<B> b); f(get_raw_a(), get_raw_a());
akan bocor jika salah satu panggilan
get_raw_a()
akan terlempar sebelum pointer mentah lainnya diikat ke parameter smart pointer-nya.Seperti yang ditunjukkan oleh TC, contohnya cacat karena konstruksi unique_ptr dari pointer mentah bersifat eksplisit, mencegah ini dari kompilasi. *
Catat juga pertanyaan klasik ini (diberi tag C , bukan C ++ ):
masih belum ditentukan.
sumber
a
, lalub
, kemudianc
, kemudiand
" dan kemudian ditampilkana(b1, b2, b3)
, menunjukkan bahwa semuab
ekspresi tidak perlu dievaluasi dalam urutan apa pun (jika tidak, akan demikiana(b, c, d)
)a(b1()(), b2()())
dapat memesanb1()()
danb2()()
dalam urutan apapun, tetapi tidak dapat melakukanb1()
makab2()()
kemudianb1()()
: hal itu mungkin tidak lagi interleave eksekusi mereka. Singkatnya, "8. ALTERNATE EVALUATION ORDER FOR FUNCTION CALLS" adalah bagian dari perubahan yang disetujui.f(i++, i)
tidak ditentukan. Sekarang tidak ditentukan. Contoh string Stroustrup mungkin tidak ditentukan, bukan tidak ditentukan. `f (get_raw_a (), get_raw_a ());` tidak akan dikompilasi karenaunique_ptr
konstruktor yang relevan eksplisit. Akhirnya,x++ + ++x
tidak ditentukan, titik.Interleaving dilarang di C ++ 17
Di C ++ 14, berikut ini tidak aman:
void foo(std::unique_ptr<A>, std::unique_ptr<B>); foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
Ada empat operasi yang terjadi di sini selama pemanggilan fungsi
new A
unique_ptr<A>
konstruktornew B
unique_ptr<B>
konstruktorUrutan ini sama sekali tidak ditentukan, dan urutan yang benar-benar valid adalah (1), (3), (2), (4). Jika urutan ini dipilih dan (3) terlempar, maka memori dari (1) bocor - kami belum menjalankan (2), yang akan mencegah kebocoran.
Di C ++ 17, aturan baru melarang interleaving. Dari [intro.execution]:
Ada catatan kaki untuk kalimat itu yang berbunyi:
Ini membuat kita memiliki dua urutan yang valid: (1), (2), (3), (4) atau (3), (4), (1), (2). Tidak ditentukan pemesanan mana yang dilakukan, tetapi keduanya aman. Semua urutan di mana (1) (3) keduanya terjadi sebelumnya (2) dan (4) sekarang dilarang.
sumber
Saya telah menemukan beberapa catatan tentang urutan evaluasi ekspresi:
Di P0145R3. Mendefinisikan Urutan Evaluasi Ekspresi untuk C ++ Idiomatik saya telah menemukan:
Tetapi saya tidak menemukannya dalam standar, sebaliknya dalam standar yang saya temukan:
Jadi, saya membandingkan perilaku yang sesuai di tiga kompiler untuk 14 dan 17 standar. Kode yang dieksplorasi adalah:
#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()); }
Hasil (yang lebih konsisten adalah dentang):
<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>
sumber