รหัสแสดงพฤติกรรมที่ไม่ระบุเนื่องจากลำดับการประเมินนิพจน์ย่อยที่ไม่ได้ระบุแม้ว่าจะไม่เรียกใช้พฤติกรรมที่ไม่ได้กำหนดเนื่องจากผลข้างเคียงทั้งหมดจะทำภายในฟังก์ชันซึ่งแนะนำความสัมพันธ์แบบลำดับระหว่างผลข้างเคียงในกรณีนี้
ตัวอย่างนี้กล่าวถึงในข้อเสนอN4228: การปรับแต่งลำดับการประเมินนิพจน์สำหรับ Idiomatic C ++ซึ่งกล่าวถึงโค้ดในคำถามต่อไปนี้:
[... ] โค้ดนี้ได้รับการตรวจสอบโดยผู้เชี่ยวชาญ C ++ ทั่วโลกและเผยแพร่ (The C ++ Programming Language, 4 th edition.) อย่างไรก็ตามความเสี่ยงต่อลำดับการประเมินที่ไม่ระบุได้ถูกค้นพบเมื่อเร็ว ๆ นี้โดยเครื่องมือ [.. .]
รายละเอียด
หลายคนอาจเห็นได้ชัดว่าอาร์กิวเมนต์ของฟังก์ชันมีลำดับการประเมินที่ไม่ระบุรายละเอียด แต่อาจไม่ชัดเจนเท่าที่พฤติกรรมนี้โต้ตอบกับการเรียกฟังก์ชันที่ถูกล่ามโซ่ มันไม่ชัดเจนสำหรับฉันเมื่อฉันวิเคราะห์กรณีนี้เป็นครั้งแรกและดูเหมือนจะไม่ใช่สำหรับผู้ตรวจสอบผู้เชี่ยวชาญทั้งหมดด้วย
เมื่อมองแวบแรกอาจดูเหมือนว่าเนื่องจากแต่ละreplace
รายการต้องได้รับการประเมินจากซ้ายไปขวาจึงต้องประเมินกลุ่มอาร์กิวเมนต์ของฟังก์ชันที่เกี่ยวข้องเป็นกลุ่มจากซ้ายไปขวาเช่นกัน
สิ่งนี้ไม่ถูกต้องอาร์กิวเมนต์ของฟังก์ชันมีลำดับการประเมินผลที่ไม่ระบุแม้ว่าการเรียกฟังก์ชันการเชื่อมโยงจะนำลำดับการประเมินจากซ้ายไปขวาสำหรับการเรียกใช้ฟังก์ชันแต่ละครั้งอาร์กิวเมนต์ของการเรียกฟังก์ชันแต่ละครั้งจะเรียงตามลำดับก่อนหน้าเท่านั้นตามการเรียกฟังก์ชันสมาชิกซึ่งเป็นส่วนหนึ่ง ของ. โดยเฉพาะอย่างยิ่งสิ่งนี้ส่งผลต่อการเรียกต่อไปนี้:
s.find( "even" )
และ:
s.find( " don't" )
ซึ่งเรียงลำดับอย่างไม่แน่นอนเกี่ยวกับ:
s.replace(0, 4, "" )
ทั้งสองfind
สายจะได้รับการประเมินก่อนหรือหลังreplace
ซึ่งเป็นเรื่องสำคัญเพราะมันมีผลข้างเคียงในการs
ในทางที่จะแก้ไขผลมาจากการที่จะเปลี่ยนความยาวของfind
s
ดังนั้นขึ้นอยู่กับว่าเมื่อใดที่replace
ได้รับการประเมินเทียบกับการfind
เรียกสองครั้งผลลัพธ์จะแตกต่างกัน
หากเราดูที่นิพจน์โซ่และตรวจสอบลำดับการประเมินของนิพจน์ย่อยบางส่วน:
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^ ^ ^ ^ ^ ^ ^ ^
A B | | | C | | |
1 2 3 4 5 6
และ:
.replace( s.find( " don't" ), 6, "" );
^ ^ ^ ^
D | | |
7 8 9
โปรดทราบว่าเรากำลังเพิกเฉยต่อความจริงที่ว่า4
และ7
สามารถแยกย่อยออกเป็นนิพจน์ย่อยเพิ่มเติมได้ ดังนั้น:
A
เป็นลำดับก่อนหน้าB
ซึ่งเรียงตามลำดับก่อนหน้าC
ซึ่งจะเรียงลำดับก่อนหลังD
1
จะ9
เรียงลำดับอย่างไม่แน่นอนเมื่อเทียบกับนิพจน์ย่อยอื่น ๆ โดยมีข้อยกเว้นบางประการที่ระบุไว้ด้านล่าง
1
ที่จะ3
มีการลำดับขั้นตอนก่อนB
4
ที่จะ6
มีการลำดับขั้นตอนก่อนC
7
ที่จะ9
มีการลำดับขั้นตอนก่อนD
กุญแจสำคัญของปัญหานี้คือ:
4
จะ9
เรียงตามลำดับอย่างไม่แน่นอนด้วยความเคารพB
การสั่งซื้อที่มีศักยภาพของทางเลือกสำหรับการประเมินผล4
และการ7
ที่เกี่ยวกับการB
อธิบายความแตกต่างในผลระหว่างclang
และเมื่อมีการประเมินgcc
f2()
ในการทดสอบของฉันclang
ประเมินB
ก่อนที่จะประเมิน4
และ7
ในขณะที่gcc
ประเมินหลังจากนั้น เราสามารถใช้โปรแกรมทดสอบต่อไปนี้เพื่อสาธิตสิ่งที่เกิดขึ้นในแต่ละกรณี:
#include <iostream>
#include <string>
std::string::size_type my_find( std::string s, const char *cs )
{
std::string::size_type pos = s.find( cs ) ;
std::cout << "position " << cs << " found in complete expression: "
<< pos << std::endl ;
return pos ;
}
int main()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
std::string copy_s = s ;
std::cout << "position of even before s.replace(0, 4, \"\" ): "
<< s.find( "even" ) << std::endl ;
std::cout << "position of don't before s.replace(0, 4, \"\" ): "
<< s.find( " don't" ) << std::endl << std::endl;
copy_s.replace(0, 4, "" ) ;
std::cout << "position of even after s.replace(0, 4, \"\" ): "
<< copy_s.find( "even" ) << std::endl ;
std::cout << "position of don't after s.replace(0, 4, \"\" ): "
<< copy_s.find( " don't" ) << std::endl << std::endl;
s.replace(0, 4, "" ).replace( my_find( s, "even" ) , 4, "only" )
.replace( my_find( s, " don't" ), 6, "" );
std::cout << "Result: " << s << std::endl ;
}
ผลการค้นหาgcc
( ดูสด )
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
ผลการค้นหาclang
( ดูสด ):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position even found in complete expression: 22
position don't found in complete expression: 33
Result: I have heard it works only if you believe in it
ผลการค้นหาVisual Studio
( ดูสด ):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
รายละเอียดจากมาตรฐาน
เราทราบดีว่าเว้นแต่จะระบุไว้การประเมินของนิพจน์ย่อยจะไม่ได้รับผลจากการดำเนินการของโปรแกรมส่วนมาตรฐาน C ++ 11ซึ่งระบุว่า:1.9
ยกเว้นในกรณีที่ระบุไว้การประเมินตัวถูกดำเนินการของแต่ละตัวดำเนินการและนิพจน์ย่อยของนิพจน์แต่ละรายการจะไม่ได้รับผลกระทบ [... ]
และเรารู้ว่าการเรียกใช้ฟังก์ชันแนะนำลำดับก่อนความสัมพันธ์ของฟังก์ชันเรียกนิพจน์ postfix และอาร์กิวเมนต์ที่เกี่ยวข้องกับเนื้อความของฟังก์ชันจากส่วน1.9
:
[... ] เมื่อเรียกใช้ฟังก์ชัน (ไม่ว่าฟังก์ชันจะอยู่ในบรรทัดหรือไม่ก็ตาม) การคำนวณค่าและผลข้างเคียงที่เกี่ยวข้องกับนิพจน์อาร์กิวเมนต์ใด ๆ หรือด้วยนิพจน์ postfix ที่กำหนดฟังก์ชันที่เรียกว่าจะเรียงลำดับก่อนดำเนินการของทุกนิพจน์หรือทุกคำสั่ง ในร่างกายของฟังก์ชันที่เรียกว่า [... ]
เรายังทราบด้วยว่าการเข้าถึงของสมาชิกชั้นเรียนดังนั้นการเชื่อมโยงจะประเมินจากซ้ายไปขวาจากส่วน5.2.5
การเข้าถึงของสมาชิกชั้นเรียนซึ่งระบุว่า:
[... ] นิพจน์ postfix ก่อนที่จะประเมินจุดหรือลูกศร; 64
ผลลัพธ์ของการประเมินพร้อมกับ id-expression จะกำหนดผลลัพธ์ของนิพจน์ postfix ทั้งหมด
หมายเหตุในกรณีที่id-expressionลงท้ายด้วยฟังก์ชันสมาชิกที่ไม่คงที่จะไม่ระบุลำดับของการประเมินนิพจน์ - ลิสต์ภายใน()
เนื่องจากเป็นนิพจน์ย่อยที่แยกต่างหาก ไวยากรณ์ที่เกี่ยวข้องจาก5.2
นิพจน์ Postfix :
postfix-expression:
postfix-expression ( expression-listopt)
postfix-expression . templateopt id-expression
การเปลี่ยนแปลง C ++ 17
ข้อเสนอp0145r3: การปรับแต่งลำดับการประเมินนิพจน์สำหรับ Idiomatic C ++ได้ทำการเปลี่ยนแปลงหลายอย่าง รวมถึงการเปลี่ยนแปลงที่ให้รหัสพฤติกรรมที่ระบุไว้อย่างดีโดยการเสริมสร้างคำสั่งของกฎการประเมินผลสำหรับpostfix แสดงออกของพวกเขาและการแสดงออกของรายการ
[expr.call] p5พูดว่า:
postfix แสดงออกเป็นลำดับขั้นตอนก่อนที่จะแสดงออกในการแสดงออกของรายการและข้อโต้แย้งใด การกำหนดค่าเริ่มต้นของพารามิเตอร์รวมถึงการคำนวณค่าที่เกี่ยวข้องและผลข้างเคียงทั้งหมดจะเรียงตามลำดับอย่างไม่แน่นอนเมื่อเทียบกับพารามิเตอร์อื่น ๆ [หมายเหตุ: ผลข้างเคียงทั้งหมดของการประเมินอาร์กิวเมนต์จะเรียงตามลำดับก่อนที่จะป้อนฟังก์ชัน (ดู 4.6) —end note] [ตัวอย่าง:
void f() {
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");
}
- ส่งตัวอย่าง]
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );