อะไรคือความแตกต่างทางแนวคิดระหว่างสุดท้ายและผู้ทำลายล้าง


12

ก่อนอื่นฉันตระหนักดีว่าเหตุใดจึงไม่มีการสร้าง 'ที่สุด' ใน C ++ แต่การอภิปรายความคิดเห็นที่เพิ่มขึ้นอย่างต่อเนื่องในคำถามอื่นดูเหมือนจะรับประกันคำถามแยกต่างหาก

นอกเหนือจากปัญหาที่finallyใน C # และ Java สามารถมีอยู่ได้เพียงครั้งเดียว (== 1) ต่อขอบเขตและขอบเขตเดียวสามารถมี destructors C ++ หลายตัว (== n) ฉันคิดว่ามันเป็นสิ่งเดียวกัน (ด้วยความแตกต่างทางเทคนิค)

อย่างไรก็ตามผู้ใช้คนอื่นแย้ง :

... ฉันพยายามที่จะบอกว่า dtor เป็นเครื่องมือสำหรับ (sematics ที่วางจำหน่าย) และในที่สุดก็เป็นเครื่องมือสำหรับ (Commit semantics) หากคุณไม่เห็นสาเหตุ: ลองคิดดูว่าทำไมการโยนข้อยกเว้นทับซ้อนทับกันในที่สุดก็เป็นเรื่องถูกกฎหมายและทำไมสิ่งเดียวกันถึงไม่ได้สำหรับผู้ทำลายล้าง (ในบางแง่มันเป็นข้อมูลเทียบกับสิ่งควบคุม Destructors สำหรับการปล่อยข้อมูลในที่สุดก็เพื่อปล่อยการควบคุมพวกมันต่างกันโชคไม่ดีที่ C ++ เชื่อมโยงพวกเขาเข้าด้วยกัน)

มีคนล้างข้อมูลนี้ได้ไหม

คำตอบ:


6
  • ธุรกรรม ( try)
  • ข้อผิดพลาดผลลัพธ์ / การตอบสนอง ( catch)
  • ข้อผิดพลาดภายนอก ( throw)
  • ข้อผิดพลาดของโปรแกรมเมอร์ ( assert)
  • ย้อนกลับ (สิ่งที่ใกล้เคียงที่สุดอาจเป็นขอบเขตของภาษาที่สนับสนุนพวกเขา)
  • ปล่อยทรัพยากร (destructors)
  • อื่น ๆ การควบคุมการทำธุรกรรม - อิสระ ( finally)

ไม่สามารถหาคำอธิบายที่ดีกว่าสำหรับfinallyโฟลว์การควบคุมที่ไม่ขึ้นกับธุรกรรม มันไม่จำเป็นต้อง map โดยตรงใด ๆ แนวคิดระดับสูงในบริบทของการทำธุรกรรมและการกู้คืนข้อผิดพลาดความคิดโดยเฉพาะอย่างยิ่งในภาษาทฤษฎีที่มีทั้ง destructors finallyและ

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

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


4

ในแบบที่พวกเขา - ในแบบเดียวกับที่เฟอร์รารีและการขนส่งสามารถใช้เพื่อเช็ดร้านค้าเพื่อดื่มนมได้ถึงแม้ว่าพวกมันจะถูกออกแบบมาเพื่อการใช้งานที่แตกต่างกัน

คุณสามารถวางลอง / สุดท้ายสร้างในทุกขอบเขตและล้างตัวแปรทั้งหมดที่กำหนดขอบเขตในบล็อกสุดท้ายเพื่อเลียนแบบ C + + destructor นี่คือแนวคิดในสิ่งที่ C ++ ทำ - คอมไพเลอร์เรียกว่า destructor โดยอัตโนมัติเมื่อตัวแปรออกจากขอบเขต (เช่นที่ส่วนท้ายของขอบเขตบล็อก) คุณต้องจัดให้มีการลองของคุณ / ในที่สุดดังนั้นความพยายามเป็นสิ่งแรกและสุดท้ายสิ่งสุดท้ายในแต่ละขอบเขตอย่างไรก็ตาม คุณจะต้องกำหนดมาตรฐานสำหรับแต่ละวัตถุที่จะมีวิธีการตั้งชื่อโดยเฉพาะที่ใช้ในการทำความสะอาดสถานะของมันที่คุณจะเรียกในบล็อกในที่สุด แต่ฉันคิดว่าคุณสามารถออกจากการจัดการหน่วยความจำปกติภาษาของคุณให้ ทำความสะอาดวัตถุที่ว่างในขณะนี้เมื่อต้องการ

มันคงไม่เป็นการดีที่จะทำเช่นนี้และแม้ว่า. NET จะแนะนำ IDispose ในฐานะ destructor ที่จัดการด้วยตนเองและการใช้บล็อคเป็นความพยายามที่จะทำให้การจัดการด้วยตนเองนั้นง่ายขึ้นเล็กน้อย แต่ก็ไม่ใช่สิ่งที่คุณต้องการทำ .


4

จากมุมมองของฉันความแตกต่างที่สำคัญคือdestructorใน c ++ เป็น กลไกโดยปริยาย (เรียกใช้โดยอัตโนมัติ) สำหรับการปล่อยทรัพยากรที่จัดสรรในขณะที่ลอง ... ในที่สุดสามารถใช้เป็นกลไกที่ชัดเจนในการทำเช่นนั้น

ในโปรแกรม c ++ โปรแกรมเมอร์จะรับผิดชอบในการปล่อยทรัพยากรที่จัดสรร สิ่งนี้มักจะนำมาใช้ใน destructor ของชั้นเรียนและทำทันทีเมื่อตัวแปรออกจากขอบเขตหรือหรือเมื่อมีการเรียกลบ

เมื่ออยู่ใน c ++ ตัวแปรโลคัลของคลาสจะถูกสร้างโดยไม่ต้องใช้newทรัพยากรของอินสแตนซ์นั้นโดยอิสระจาก destructor เมื่อมีข้อยกเว้น

// c++
void test() {
    MyClass myClass(someParameter);
    // if there is an exception the destructor of MyClass is called automatically
    // this does not work with
    // MyClass* pMyClass = new MyClass(someParameter);

} // on test() exit the destructor of myClass is implicitly called

ใน java, c # และระบบอื่น ๆ ที่มีการจัดการหน่วยความจำอัตโนมัติระบบเก็บขยะจะตัดสินใจเมื่ออินสแตนซ์ของคลาสถูกทำลาย

// c#
void test() {
    MyClass myClass = new MyClass(someParameter);
    // if there is an exception myClass is NOT destroyed so there may be memory/resource leakes

    myClass.destroy(); // this is never called
}

ไม่มีกลไกโดยปริยายที่จะทำให้คุณต้องโปรแกรมนี้อย่างชัดเจนโดยใช้ลองในที่สุด

// c#
void test() {
    MyClass myClass = null;

    try {
        myClass = new MyClass(someParameter);
        ...
    } finally {
        // explicit memory management
        // even if there is an exception myClass resources are freed
        myClass.destroy();
    }

    myClass.destroy(); // this is never called
}

ใน C ++ เหตุใด destructor จึงถูกเรียกโดยอัตโนมัติเฉพาะกับวัตถุสแต็กและไม่ใช่วัตถุฮีปในกรณีที่มีข้อยกเว้น
Giorgio

@Giorgio เนื่องจากทรัพยากรฮีปอาศัยอยู่ในพื้นที่หน่วยความจำที่ไม่ได้เชื่อมโยงโดยตรงกับสแต็คการโทร ตัวอย่างเช่นสมมติว่าเป็นโปรแกรมแบบมัลติเธรดมี 2 กระทู้, และA Bหากมีเธรดหนึ่งA'sรายการธุรกรรมย้อนกลับไม่ควรทำลายทรัพยากรที่จัดสรรBเช่น - สถานะของเธรดนั้นเป็นอิสระจากกัน อย่างไรก็ตามโดยทั่วไปใน C ++ หน่วยความจำฮีปยังคงเชื่อมโยงกับวัตถุในสแต็ก

@Giorgio ตัวอย่างเช่นstd::vectorวัตถุอาจมีชีวิตอยู่บนสแต็ก แต่ชี้ไปที่หน่วยความจำบนฮีป - ทั้งวัตถุเวกเตอร์ (บนสแต็ก) และเนื้อหาของมัน (บนฮีป) จะถูกจัดสรรคืนในระหว่างสแต็กคลายในกรณีนั้นเนื่องจาก การทำลายเวกเตอร์บนสแต็กจะเรียก destructor ซึ่งปลดปล่อยหน่วยความจำที่เกี่ยวข้องบนฮีป (และทำลายองค์ประกอบฮีปเหล่านั้นเช่นเดียวกัน) โดยทั่วไปแล้วสำหรับข้อยกเว้นด้านความปลอดภัยวัตถุ C ++ ส่วนใหญ่จะอยู่บนสแต็กแม้ว่าจะเป็นเพียงการจัดการที่ชี้ไปยังหน่วยความจำบนฮีปโดยอัตโนมัติกระบวนการของการปลดปล่อยทั้งฮีปและหน่วยความจำสแต็ก

4

ดีใจที่คุณโพสต์ข้อความนี้เป็นคำถาม :)

ฉันพยายามจะบอกว่า destructors finallyนั้นแตกต่างจากความคิด:

  • Destructors สำหรับการปล่อยทรัพยากร ( ข้อมูล )
  • finallyสำหรับการกลับไปที่ผู้โทร ( ตัวควบคุม )

ลองพิจารณาสมมุติว่าใช้โค้ดหลอกนี้

try {
    bar();
} finally {
    logfile.print("bar has exited...");
}

finallyนี่คือการแก้ปัญหาการควบคุมทั้งหมดและไม่ใช่ปัญหาการจัดการทรัพยากร
มันคงไม่มีเหตุผลที่จะทำเช่นนั้นในผู้ทำลายล้างด้วยเหตุผลหลายประการ:

  • ไม่มีสิ่งใดที่ "ได้มา" หรือ "สร้าง"
  • ความล้มเหลวในการพิมพ์ไปยังล็อกไฟล์จะไม่ส่งผลให้เกิดการรั่วไหลของทรัพยากรความเสียหายของข้อมูล ฯลฯ (สมมติว่าไฟล์บันทึกการทำงานที่นี่ไม่ได้ถูกป้อนเข้าสู่โปรแกรมอื่น)
  • มันถูกต้องตามกฎหมายที่logfile.printจะล้มเหลวในขณะที่การทำลาย (แนวคิด) ไม่สามารถล้มเหลว

นี่เป็นอีกตัวอย่างหนึ่งคราวนี้เหมือนกับใน Javascript:

var mo_document = document, mo;
function observe(mutations) {
    mo.disconnect();  // stop observing changes to prevent re-entrance
    try {
        /* modify stuff */
    } finally {
        mo.observe(mo_document);  // continue observing (conceptually, this can fail)
    }
}
mo = new MutationObserver(observe);
return observe();

ในตัวอย่างข้างต้นอีกครั้งไม่มีทรัพยากรที่จะได้รับการปล่อยตัว
ในความเป็นจริงfinallyบล็อกกำลังรับทรัพยากรภายในเพื่อให้บรรลุเป้าหมายซึ่งอาจเกิดความล้มเหลว ดังนั้นจึงไม่เหมาะสมที่จะใช้ตัวทำลาย (ถ้ามี Javascript)

ในอีกทางหนึ่งในตัวอย่างนี้:

b = get_data();
try {
    a.write(b);
} finally {
    free(b);
}

finallyกำลังทำลายทรัพยากร, b. มันเป็นปัญหาด้านข้อมูล ปัญหาไม่ได้เกี่ยวกับการคืนค่าการควบคุมไปยังผู้เรียกอย่างชัดเจน แต่เป็นการหลีกเลี่ยงการรั่วไหลของทรัพยากร
ความล้มเหลวไม่ใช่ตัวเลือกและไม่ควรเกิดขึ้น (แนวคิด)
การเปิดตัวทุกครั้งbจำเป็นต้องจับคู่กับการซื้อกิจการและจำเป็นต้องใช้ RAII

กล่าวอีกนัยหนึ่งเพียงเพราะคุณสามารถใช้เพื่อจำลองทั้งที่ไม่ได้หมายความว่าทั้งสองเป็นหนึ่งและปัญหาเดียวกันหรือว่าทั้งสองเป็นทางออกที่เหมาะสมสำหรับปัญหาทั้งสอง


ขอบคุณ ฉันไม่เห็นด้วย แต่เฮ้ :-) ฉันคิดว่าฉันจะสามารถเพิ่มคำตอบที่ตรงข้ามกับคำตอบได้อย่างละเอียดในวันถัดไป ...
Martin Ba

2
ความจริงที่ว่าfinallyส่วนใหญ่จะใช้เพื่อปล่อยปัจจัยทรัพยากร (ไม่ใช่หน่วยความจำ) ลงในสิ่งนี้?
Bart van Ingen Schenau

1
@BartvanIngenSchenau: ฉันไม่เคยโต้แย้งว่าภาษาใด ๆ ที่มีอยู่ในปัจจุบันมีปรัชญาหรือการดำเนินการที่ตรงกับสิ่งที่ฉันอธิบาย ผู้คนยังไม่ได้ประดิษฐ์ทุกอย่างที่อาจมีอยู่ ฉันแค่แย้งว่าจะมีค่าในการแยกความคิดทั้งสองเพราะพวกเขาเป็นความคิดที่แตกต่างกันและมีกรณีการใช้งานที่แตกต่างกัน เพื่อตอบสนองความอยากรู้ของคุณฉันเชื่อว่าDมีทั้งคู่ อาจมีภาษาอื่นด้วย ผมไม่คิดว่ามันแม้ว่าที่เกี่ยวข้องและผมก็ไม่สามารถดูแลน้อยว่าทำไมเช่น Java finallyอยู่ในความโปรดปรานของ
user541686

1
ตัวอย่างที่ใช้งานได้จริงที่ฉันพบใน JavaScript คือฟังก์ชั่นที่เปลี่ยนตัวชี้เมาส์ไปเป็นนาฬิกาทรายชั่วคราวในระหว่างการดำเนินการที่มีความยาว (ซึ่งอาจทำให้เกิดข้อยกเว้น) จากนั้นเปลี่ยนกลับเป็นปกติในfinallyข้อ C ++ worldview จะแนะนำคลาสที่จัดการ“ ทรัพยากร” นี้ของการมอบหมายให้กับตัวแปร pseudo-global ความรู้สึกทางแนวคิดอะไรที่ทำให้? แต่ destructors เป็นค้อนของ C ++ สำหรับการประมวลผลโค้ดปลายทางที่ต้องการ
dan04

1
@ dan04: ขอบคุณมากนั่นเป็นตัวอย่างที่สมบูรณ์แบบสำหรับสิ่งนี้ ฉันสาบานได้ว่าจะเจอสถานการณ์มากมายที่ RAII ไม่เข้าท่า แต่ฉันคิดว่ามันลำบาก
user541686

1

คำตอบของ k3b จริงๆวลี:

destructor ใน c ++ เป็นกลไกโดยนัย (เรียกใช้โดยอัตโนมัติ) สำหรับการปล่อยทรัพยากรที่จัดสรรในขณะที่ลอง ... ในที่สุดก็สามารถใช้เป็นกลไกที่ชัดเจนในการทำเช่นนั้น

ในฐานะที่เป็น "ทรัพยากร" ผมชอบที่จะอ้างถึงจอน Kalb: RAII ควรจะหมายถึงความรับผิดชอบที่ซื้อกิจการคือการเริ่มต้น

อย่างไรก็ตามสำหรับนัยกับชัดเจนนี่ดูเหมือนจะเป็นจริง:

  • d'tor เป็นเครื่องมือในการกำหนดการดำเนินการที่จะเกิดขึ้นโดยปริยายเมื่ออายุการใช้งานของวัตถุสิ้นสุดลง (ซึ่งมักจะเกิดขึ้นพร้อมกับจุดสิ้นสุดของขอบเขต)
  • ในที่สุดบล็อกเป็นเครื่องมือในการกำหนด - อย่างชัดเจน - การดำเนินงานที่จะเกิดขึ้นในตอนท้ายของขอบเขต
  • นอกจากนี้ในทางเทคนิคคุณได้รับอนุญาตให้โยนจากในที่สุด แต่ดูด้านล่าง

ฉันคิดว่ามันเป็นส่วนที่เกี่ยวกับความคิด ...


... ตอนนี้มีรายละเอียดที่น่าสนใจ IMHO:

  • ลอง / จับ / ในที่สุดก็ขาดที่ไม่มีfaultบล็อกเพื่อเรียกใช้รหัสที่ควรทำงานในกรณีที่มีการออกขอบเขตพิเศษ
  • destructors สามารถนำมาใช้ในการสร้างเครื่องจักรในที่สุด - ดูSCOPE_EXIT, SCOPE_FAILและในห้องสมุดความเขลาSCOPE_SUCCESS ดูAndrei Alexandrescu: การจัดการข้อผิดพลาดในโฟลว์ C ++ / Declarative Control (จัดขึ้นที่NDC 2014 )
  • SCOPE_EXIT สามารถทำซ้ำได้มากในที่สุดเนื่องจากเป็นเทคนิคที่ดีที่สุดในการโยนจากในขณะที่มันก็ไม่ได้ทำงานได้อย่างน่าเชื่อถือจาก destructor
  • แต่ก็จะดูเหมือนกับผมว่าคนมักจะคิดว่าการขว้างปาจากภายในในที่สุดก็เป็นความคิดที่ดีอยู่แล้ว

ฉันยังไม่คิดว่า c'tor / d'tor ต้องมีแนวคิด "ได้รับ" หรือ "สร้าง" อะไรนอกเหนือจากความรับผิดชอบในการเรียกใช้รหัสบางอย่างใน destructor ซึ่งเป็นสิ่งที่ในที่สุดก็ทำ: เรียกใช้รหัส

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

(นอกจากนี้ฉันไม่มั่นใจเลยว่ารหัสที่ "ดี" ควรทิ้งในที่สุด - อาจเป็นคำถามทั้งหมดอีกข้อหนึ่งของตัวเอง)


คุณคิดอย่างไรกับตัวอย่างจาวาสคริปต์ของฉัน
user541686

เกี่ยวกับข้อโต้แย้งอื่น ๆ ของคุณ: "เราต้องการบันทึกสิ่งเดียวกันโดยไม่คำนึงถึงสิ่งนั้นหรือไม่?" ใช่มันเป็นเพียงตัวอย่างและคุณพลาดจุดและใช่ไม่มีใครถูกห้ามไม่ให้บันทึกรายละเอียดที่เฉพาะเจาะจงมากขึ้นสำหรับแต่ละกรณี ประเด็นตรงนี้คือคุณไม่สามารถอ้างสิทธิ์ได้อย่างแน่นอนว่าไม่เคยมีสถานการณ์ที่คุณต้องการบันทึกสิ่งที่เป็นเรื่องปกติของทั้งสองอย่าง รายการบันทึกบางรายการเป็นข้อมูลทั่วไปบางรายการเป็นรายการเฉพาะ คุณต้องการทั้ง และอีกครั้งคุณเป็นจุดที่ขาดหายไปโดยสิ้นเชิงโดยมุ่งเน้นไปที่การเข้าสู่ระบบ การสร้างแรงจูงใจตัวอย่าง 10 บรรทัดนั้นยาก โปรดพยายามอย่าพลาดประเด็น
user541686

คุณไม่เคยพูดถึงสิ่งเหล่านี้ ...
user541686

@ Mehrdad - ฉันไม่ได้ระบุจาวาสคริปต์ของคุณเพราะมันจะพาฉันไปอีกหน้าเพื่อพูดคุยเกี่ยวกับสิ่งที่ฉันคิด (ฉันพยายาม แต่ฉันใช้เวลานานมากในการพูดบางสิ่งที่เชื่อมโยงกันซึ่งฉันข้ามมันไป :-)
Martin Ba

@ Mehrdad - สำหรับประเด็นอื่น ๆ ของคุณ - ดูเหมือนว่าเราจะต้องเห็นด้วยที่จะไม่เห็นด้วย ผมเห็นว่าคุณกำลังเล็งไปที่มีความแตกต่าง แต่ฉันก็ไม่เชื่อว่าพวกเขามีบางสิ่งบางอย่างที่แตกต่างกันแนวคิด: ส่วนใหญ่เพราะผมส่วนใหญ่อยู่ในค่ายที่คิดว่าการขว้างปาจากในที่สุดก็เป็นความคิดที่ไม่ดีจริงๆ ( หมายเหตุ : ฉันยัง คิดในobserverตัวอย่างของคุณว่าการขว้างปาจะมีความคิดที่ไม่ดีจริงๆ) อย่าลังเลที่จะเปิดการแชทหากคุณต้องการพูดคุยเรื่องนี้เพิ่มเติม แน่นอนมันสนุกที่จะคิดถึงข้อโต้แย้งของคุณ ไชโย
Martin Ba
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.