ลำดับการดำเนินการ C ++ ในวิธีการผูกมัด


108

ผลลัพธ์ของโปรแกรมนี้:

#include <iostream> 
class c1
{   
  public:
    c1& meth1(int* ar) {
      std::cout << "method 1" << std::endl;
      *ar = 1;
      return *this;
    }
    void meth2(int ar)
    {
      std::cout << "method 2:"<< ar << std::endl;
    }
};

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu).meth2(nu);
}

คือ:

method 1
method 2:0

ทำไมnuไม่เป็น1 เมื่อmeth2()เริ่ม?


42
@MartinBonner: แม้ว่าฉันจะรู้คำตอบ แต่ฉันก็จะไม่เรียกมันว่า "ชัดเจน" ในความหมายของคำใด ๆและแม้ว่าจะเป็นเช่นนั้นนั่นก็ไม่ใช่เหตุผลที่ดีในการโหวตโดยการโหวตลงคะแนน น่าผิดหวัง!
Lightness Races ใน Orbit

4
นี่คือสิ่งที่คุณจะได้รับเมื่อคุณแก้ไขข้อโต้แย้งของคุณ ฟังก์ชั่นที่แก้ไขข้อโต้แย้งนั้นอ่านยากกว่าผลของมันเป็นสิ่งที่ไม่คาดคิดสำหรับโปรแกรมเมอร์คนต่อไปที่จะทำงานกับโค้ดและทำให้เกิดความประหลาดใจเช่นนี้ ฉันขอแนะนำอย่างยิ่งให้หลีกเลี่ยงการแก้ไขพารามิเตอร์ใด ๆ ยกเว้น Invocant การแก้ไข Invocant จะไม่เป็นปัญหาที่นี่เพราะวิธีที่สองถูกเรียกใช้จากผลลัพธ์ของครั้งแรกดังนั้นเอฟเฟกต์จึงถูกเรียงลำดับ ยังมีบางกรณีที่พวกเขาจะไม่เป็นเช่นนั้น
ม.ค. Hudec


@ JanHudec นี่คือเหตุผลที่การเขียนโปรแกรมเชิงฟังก์ชันให้ความสำคัญอย่างมากกับความบริสุทธิ์ของฟังก์ชัน
Pharap

2
ตัวอย่างเช่นรูปแบบการเรียกแบบสแต็กอาจต้องการพุnu&nuและcไปยังสแต็กตามลำดับนั้นจากนั้นเรียกใช้meth1พุชผลลัพธ์ไปยังสแต็กจากนั้นเรียกใช้meth2ในขณะที่แบบฟอร์มการโทรแบบลงทะเบียนต้องการ โหลดcและ&nuลงในทะเบียนวิงวอนmeth1โหลดลงในทะเบียนแล้ววิงวอนnu meth2
Neil

คำตอบ:


66

เนื่องจากไม่ได้ระบุลำดับการประเมิน

คุณกำลังเห็นnuในmainการประเมิน0ก่อนที่จะmeth1ถูกเรียกด้วยซ้ำ นี่คือปัญหาเกี่ยวกับการผูกมัด ฉันไม่แนะนำให้ทำ

เพียงสร้างโปรแกรมที่ดีเรียบง่ายชัดเจนอ่านง่ายเข้าใจง่าย:

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu);
  c.meth2(nu);
}

14
มีความเป็นไปได้ที่ข้อเสนอเพื่อชี้แจงลำดับการประเมินในบางกรณีซึ่งแก้ไขปัญหานี้จะเกิดขึ้นสำหรับ C ++ 17
Revolver_Ocelot

7
ฉันชอบวิธีการผูกมัด (เช่น<<สำหรับเอาต์พุตและ "ตัวสร้างอ็อบเจ็กต์" สำหรับอ็อบเจ็กต์เชิงซ้อนที่มีอาร์กิวเมนต์กับคอนสตรัคเตอร์มากเกินไป - แต่มันผสมกับอาร์กิวเมนต์เอาต์พุตได้ไม่ดีนัก
Martin Bonner สนับสนุน Monica

34
ฉันเข้าใจถูกไหม ลำดับการประเมินmeth1และmeth2กำหนดไว้ แต่การประเมินพารามิเตอร์สำหรับmeth2อาจเกิดขึ้นก่อนmeth1เรียกว่า ... ?
Roddy

7
การเชื่อมโยงวิธีนั้นใช้ได้ตราบเท่าที่วิธีการนั้นเหมาะสมและแก้ไขเฉพาะ Invocant เท่านั้น (ซึ่งมีการเรียงลำดับเอฟเฟกต์อย่างดีเนื่องจากวิธีที่สองเรียกตามผลลัพธ์ของครั้งแรก)
ม.ค. Hudec

4
มันเป็นตรรกะเมื่อคุณคิดเกี่ยวกับมัน ทำงานเหมือนmeth2(meth1(c, &nu), nu)
BartekChom

29

ฉันคิดว่าส่วนนี้ของร่างมาตรฐานเกี่ยวกับลำดับการประเมินมีความเกี่ยวข้อง:

1.9 การดำเนินการโปรแกรม

...

  1. ยกเว้นในกรณีที่ระบุไว้การประเมินค่าตัวถูกดำเนินการของแต่ละตัวดำเนินการและนิพจน์ย่อยของนิพจน์แต่ละรายการจะไม่ได้รับผลกระทบ การคำนวณค่าของตัวถูกดำเนินการของตัวดำเนินการจะเรียงลำดับก่อนการคำนวณค่าของผลลัพธ์ของตัวดำเนินการ หากผลข้างเคียงที่มีต่อวัตถุสเกลาร์ไม่สัมพันธ์กับผลข้างเคียงอื่นที่มีต่อวัตถุสเกลาร์เดียวกันหรือการคำนวณค่าโดยใช้ค่าของวัตถุสเกลาร์เดียวกันและอาจไม่เกิดขึ้นพร้อมกันพฤติกรรมจะไม่ถูกกำหนด

และนอกจากนี้ยังมี:

5.2.2 การเรียกใช้ฟังก์ชัน

...

  1. [หมายเหตุ: การประเมินของนิพจน์ postfix และอาร์กิวเมนต์จะไม่สัมพันธ์กัน ผลข้างเคียงทั้งหมดของการประเมินอาร์กิวเมนต์จะถูกจัดลำดับก่อนที่จะป้อนฟังก์ชัน - หมายเหตุท้าย]

ดังนั้นสำหรับสายของคุณc.meth1(&nu).meth2(nu);ให้พิจารณาสิ่งที่เกิดขึ้นในโอเปอเรเตอร์ในแง่ของตัวดำเนินการเรียกฟังก์ชันสำหรับการเรียกครั้งสุดท้ายเพื่อmeth2ให้เราเห็นรายละเอียดอย่างชัดเจนในนิพจน์ postfix และอาร์กิวเมนต์nu:

operator()(c.meth1(&nu).meth2, nu);

การประเมินของนิพจน์ postfix และอาร์กิวเมนต์สำหรับการเรียกใช้ฟังก์ชันสุดท้าย (เช่นนิพจน์ postfix c.meth1(&nu).meth2และnu) จะไม่สัมพันธ์กันตามกฎการเรียกฟังก์ชันข้างต้น ดังนั้นผลข้างเคียงของการคำนวณนิพจน์ postfix บนวัตถุสเกลาร์arจะไม่สัมพันธ์กับการประเมินอาร์กิวเมนต์nuก่อนการmeth2เรียกใช้ฟังก์ชัน ตามกฎการทำงานของโปรแกรมด้านบนนี่คือพฤติกรรมที่ไม่ได้กำหนดไว้

กล่าวอีกนัยหนึ่งก็คือไม่มีข้อกำหนดสำหรับคอมไพลเลอร์ในการประเมินnuอาร์กิวเมนต์ของการmeth2เรียกหลังจากการmeth1โทร - มีอิสระที่จะถือว่าไม่มีผลข้างเคียงที่จะmeth1ส่งผลต่อการnuประเมินผล

รหัสแอสเซมบลีที่ผลิตโดยด้านบนประกอบด้วยลำดับต่อไปนี้ในmainฟังก์ชัน:

  1. ตัวแปรnuถูกจัดสรรบนสแต็กและเริ่มต้นด้วย 0
  2. การลงทะเบียน ( ebxในกรณีของฉัน) ได้รับสำเนาค่าของnu
  3. ที่อยู่ของnuและcโหลดลงในรีจิสเตอร์พารามิเตอร์
  4. meth1 ถูกเรียก
  5. รีจิสเตอร์ค่าส่งคืนและค่าแคชก่อนหน้านี้ของnuในebxรีจิสเตอร์ถูกโหลดลงในรีจิสเตอร์พารามิเตอร์
  6. meth2 ถูกเรียก

ในขั้นตอนที่ 5 เหนือคอมไพเลอร์อนุญาตให้ใช้ค่าแคชของ nuจากขั้นตอนที่ 2 meth2จะเป็นอีกครั้งที่ใช้ในการเรียกฟังก์ชั่น ในที่นี้จะไม่คำนึงถึงความเป็นไปได้ที่nuอาจมีการเปลี่ยนแปลงโดยการเรียกร้องให้meth1- 'พฤติกรรมที่ไม่ได้กำหนด' ในการดำเนินการ

หมายเหตุ:คำตอบนี้มีการเปลี่ยนแปลงในเนื้อหาจากรูปแบบเดิม คำอธิบายเริ่มต้นของฉันในแง่ของผลข้างเคียงของการคำนวณตัวถูกดำเนินการที่ไม่ได้รับการจัดลำดับก่อนที่จะเรียกฟังก์ชันสุดท้ายไม่ถูกต้องเนื่องจากเป็น ปัญหาคือความจริงที่ว่าการคำนวณของตัวถูกดำเนินการเองนั้นมีการเรียงลำดับอย่างไม่แน่นอน


2
นี่เป็นสิ่งที่ไม่ถูกต้อง การเรียกใช้ฟังก์ชันจะเรียงตามลำดับอย่างไม่แน่นอนโดยไม่มีการประเมินผลอื่น ๆ ในฟังก์ชันการโทร (เว้นแต่จะมีการกำหนดข้อ จำกัด ตามลำดับก่อนหลัง) พวกเขาไม่แทรกกลาง
TC

1
@TC - ฉันไม่เคยพูดอะไรเกี่ยวกับการเรียกฟังก์ชั่นที่สอดแทรก ฉันอ้างถึงผลข้างเคียงของตัวดำเนินการเท่านั้น หากคุณดูรหัสแอสเซมบลีที่สร้างขึ้นข้างต้นคุณจะเห็นว่าmeth1มีการดำเนินการก่อนหน้าmeth2นี้ แต่พารามิเตอร์สำหรับmeth2คือค่าของnuแคชในรีจิสเตอร์ก่อนที่จะเรียกไปmeth1- นั่นคือคอมไพเลอร์ได้ละเว้นผลข้างเคียงที่อาจเกิดขึ้นซึ่งก็คือ สอดคล้องกับคำตอบของฉัน
Smeeheey

1
คุณอ้างว่า - "ผลข้างเคียง (เช่นการตั้งค่าของ ar) ไม่รับประกันว่าจะเรียงตามลำดับก่อนการเรียก" การประเมินผลของ postfix-expression ในการเรียกใช้ฟังก์ชัน (ซึ่งเป็นc.meth1(&nu).meth2) และการประเมินอาร์กิวเมนต์ของการเรียกนั้น ( nu) โดยทั่วไปจะไม่มีผลตามมา แต่ 1) ผลข้างเคียงจะเรียงตามลำดับทั้งหมดก่อนที่จะเข้าสู่meth2และ 2) เนื่องจากc.meth1(&nu)เป็นการเรียกใช้ฟังก์ชัน ก็จะติดใจ indeterminately nuกับการประเมินผลของ ข้างในmeth2ถ้ามันมีตัวชี้ไปที่ตัวแปรในmainมันจะเห็น 1 เสมอ
TC

2
"อย่างไรก็ตามผลข้างเคียงของการคำนวณตัวถูกดำเนินการ (เช่นการตั้งค่าของ ar) ไม่รับประกันว่าจะเรียงลำดับก่อนสิ่งใดเลย (ตาม 2) ด้านบน)" รับประกันอย่างแน่นอนว่าจะเรียงตามลำดับก่อนที่จะโทรไปmeth2ตามที่ระบุไว้ในข้อ 3 ของหน้า cppreference ที่คุณอ้างถึง (ซึ่งคุณละเลยที่จะอ้างอิงอย่างถูกต้องด้วย)
TC

1
คุณทำอะไรผิดพลาดและทำให้แย่ลง ไม่มีพฤติกรรมที่ไม่ได้กำหนดไว้ที่นี่อย่างแน่นอน อ่านต่อไป [intro.execution] / 15 ผ่านตัวอย่าง
TC

9

ในมาตรฐาน C ++ ปี 1998 ส่วนที่ 5 ย่อหน้าที่ 4

ยกเว้นในกรณีที่ระบุไว้ลำดับของการประเมินตัวถูกดำเนินการของแต่ละตัวดำเนินการและนิพจน์ย่อยของแต่ละนิพจน์และลำดับของผลข้างเคียงที่เกิดขึ้นไม่ได้ระบุไว้ ระหว่างจุดลำดับก่อนหน้าและลำดับถัดไปวัตถุสเกลาร์จะต้องมีการแก้ไขค่าที่เก็บไว้มากที่สุดครั้งเดียวโดยการประเมินนิพจน์ นอกจากนี้ค่าก่อนหน้าจะถูกเข้าถึงเพื่อกำหนดค่าที่จะจัดเก็บเท่านั้น ข้อกำหนดของย่อหน้านี้จะต้องเป็นไปตามสำหรับแต่ละลำดับที่อนุญาตของนิพจน์ย่อยของนิพจน์แบบเต็ม มิฉะนั้นพฤติกรรมจะไม่ถูกกำหนด

(ฉันได้ละเว้นการอ้างอิงเชิงอรรถ # 53 ซึ่งไม่เกี่ยวข้องกับคำถามนี้)

โดยพื้นฐานแล้ว&nuจะต้องมีการประเมินก่อนที่จะโทรc1::meth1()และต้องได้รับการประเมินก่อนที่จะโทรnu c1::meth2()อย่างไรก็ตามไม่มีข้อกำหนดใดที่nuได้รับการประเมินมาก่อน&nu(เช่นได้รับอนุญาตให้nuประเมินก่อนจาก&nuนั้นc1::meth1()จึงเรียกว่า - ซึ่งอาจเป็นสิ่งที่คอมไพเลอร์ของคุณกำลังทำอยู่) ดังนั้นจึงไม่รับประกันว่านิพจน์*ar = 1ในc1::meth1()จะได้รับการประเมินก่อนnuในmain()จะได้รับการประเมินเพื่อส่งต่อc1::meth2()การประเมินเพื่อที่จะถูกส่งผ่านไป

มาตรฐาน C ++ ในภายหลัง (ซึ่งตอนนี้ฉันไม่มีบนพีซีที่ฉันใช้คืนนี้) มีประโยคเดียวกันเป็นหลัก


7

ฉันคิดว่าเมื่อรวบรวมก่อนที่ funtions meth1 และ meth2 จะถูกเรียกจริงๆพารามีเทอร์ถูกส่งต่อไปยังพวกเขา ฉันหมายถึงเมื่อคุณใช้ "c.meth1 (& nu) .meth2 (nu);" ค่า nu = 0 ถูกส่งต่อไปยัง meth2 ดังนั้นจึงไม่สำคัญว่า "nu" จะถูกเปลี่ยนตามหลัง

คุณสามารถลองสิ่งนี้:

#include <iostream> 
class c1
{
public:
    c1& meth1(int* ar) {
        std::cout << "method 1" << std::endl;
        *ar = 1;
        return *this;
    }
    void meth2(int* ar)
    {
        std::cout << "method 2:" << *ar << std::endl;
    }
};

int main()
{
    c1 c;
    int nu = 0;
    c.meth1(&nu).meth2(&nu);
    getchar();
}

จะได้คำตอบที่คุณต้องการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.