หน่วยความจำแบบไดนามิกและข้อยกเว้นของตัวสร้าง

วันนี้ฉันค้นพบฟังก์ชั่น try-catch บล็อก (จาก ที่นี่ อันที่จริง) และ จากนั้นก็สนุกสนานไปกับการวิจัยเล็กน้อย - เห็นได้ชัดว่าพวกเขากำลังใช้งานหลักคือมันจับข้อยกเว้นโดยรายการตัวสร้างเริ่มต้น

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


รับโค้ดตัวอย่างนี้:

class A
{
private:
    B b
    C *c;    //classes B, C & D omitted for brevity as not really relevant
    D d;
public
    A(int x, int y, int z)
};

A::A(int x, int y, int z)
try
    : b( x )
    , c( new C(y) )
    , d( z )
{
    //omitted
}
catch(...)
{
    //omitted
}

จะเกิดอะไรขึ้นในกรณีเหล่านี้:

  1. การเริ่มต้นของ b ทำให้เกิดข้อยกเว้น
  2. การเริ่มต้นของ c ทำให้เกิดข้อยกเว้น
  3. การเริ่มต้นของ d ทำให้เกิดข้อยกเว้น

โดยเฉพาะอย่างยิ่ง ฉันต้องการทราบอย่างน้อย:

  • อะไรจะ/อาจทำให้หน่วยความจำรั่วจาก new C(y) ฉันกำลังคิดแค่ 3 เท่านั้นเหรอ? (ดูที่นี่)
  • คุณสามารถ delete b ในระยะจับได้ไหม? เป็นอันตรายในกรณีที่ 1 และ 2 หรือไม่

แน่นอน ฉันเดาว่าสิ่งที่ปลอดภัยที่สุดที่ต้องทำคือทำให้ c เป็นตัวชี้อัจฉริยะ แต่การเพิกเฉยต่อตัวเลือกนั้นในขณะนี้ อะไรคือแนวทางปฏิบัติที่ดีที่สุด?

ปลอดภัยหรือไม่ที่จะตั้งค่า c เป็น NULL ในค่าเริ่มต้น จากนั้นจึงทำการเรียกไปที่ new ในเนื้อหาของ Constructor

นั่นหมายความว่าจะต้องวาง delete c ไว้ใน catch ในกรณีที่มีอย่างอื่นพุ่งเข้าไปในตัว Constructor มีปัญหาด้านความปลอดภัยในการทำเช่นนั้นหรือไม่ (เช่น ถ้าเป็น c = new C(y); เองที่พ่น)


person DMA57361    schedule 05.07.2010    source แหล่งที่มา
comment
คุณไม่สามารถพึ่งพาการตั้งค่า c เป็น NULL ได้ เพราะถ้า b ส่งข้อยกเว้น c จะไม่ถูกกำหนดเป็น NULL ณ จุดนั้น อย่างที่คุณพูดให้ใช้ตัวชี้อัจฉริยะ   -  person 5ound    schedule 05.07.2010
comment
@ 5ound - มาก จุดดีที่ฉันไม่คิดว่าจะมี   -  person DMA57361    schedule 05.07.2010


คำตอบ (3)


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

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

โดยทั่วไปแล้ว การลบพอยน์เตอร์ออกจากบล็อก catch นั้นไม่ปลอดภัย เนื่องจากไม่มีการรับประกันมูลค่าที่แท้จริงของพอยน์เตอร์ ก่อน การเริ่มต้นจะดำเนินการ นั่นคือ หาก b หรือ c โยน อาจเป็นกรณีที่ c ไม่ใช่ 0 แต่ก็ไม่ใช่ตัวชี้ที่ถูกต้องเช่นกัน และการลบออกจะเป็นลักษณะการทำงานที่ไม่ได้กำหนดไว้ เช่นเคย มีหลายกรณี ราวกับว่าคุณรับประกันว่าทั้ง b และ c จะไม่โยนทิ้ง และสมมติว่า bad_alloc ไม่ใช่สิ่งที่คุณมักจะกู้คืนมา ก็อาจจะปลอดภัย

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

person David Rodríguez - dribeas    schedule 05.07.2010
comment
ใช่ ดูเหมือนว่ายิ่งฉันคิดและอ่านเกี่ยวกับสิ่งเหล่านี้มากเท่าไร ดูเหมือนว่าพวกเขาไม่ได้ช่วยอะไรได้มากนักเท่านั้น - person DMA57361; 05.07.2010
comment
บล็อก try/catch การอ้างสิทธิ์ของคุณถูกขมวดคิ้ว เช่นเดียวกับที่ goto... มันเป็นเรื่องไร้สาระ try/catch เป็นคุณลักษณะภาษา ที่ใช้ในการเขียนโค้ดพร้อมการจัดการข้อผิดพลาดที่เหมาะสมในบริบทของข้อยกเว้น คุณคาดหวังให้จัดการข้อยกเว้นอย่างไร - person hkaiser; 05.07.2010
comment
@hkaiser: มีความเข้าใจผิดที่นี่ สิ่งที่ ขมวดคิ้ว คือบล็อก try/catch ระดับฟังก์ชัน: void f() try { ... } catch () {} คุณลักษณะนั้นถูกเพิ่มเข้าไปในภาษาและค่อนข้างจะไม่รู้จัก และไม่แนะนำ ฉันได้ชี้แจงเจตนา – หรืออย่างน้อยก็พยายามแล้ว – ตอนนี้ ฟังก์ชัน try/catch บล็อก ภายใน ปกติคือคุณลักษณะภาษา สำหรับการจัดการข้อผิดพลาดตามที่คุณระบุอย่างถูกต้อง ฉันจะพยายามค้นหาข้อมูลจากแหล่งที่เป็นที่รู้จักเมื่อฉันกลับถึงบ้าน นี่เป็นอันแรก: GOTW#66 - person David Rodríguez - dribeas; 05.07.2010
comment
@David - ลิงก์นั้นในความคิดเห็นของคุณค่อนข้างมีประโยชน์ - และนี่คือคำพูดจากส่วน 'ศีลธรรม' (ระหว่าง 3 และ 4) ซึ่งฉันคิดว่าให้คำตอบ: ...หรือใหม่[] ในตัวสร้างที่มัน สามารถทำความสะอาดได้อย่างปลอดภัยโดยใช้ try-block ในเครื่องหรืออย่างอื่น สำหรับฉันนั่นฟังดูเหมือนทางออกที่ดีที่สุด (ไม่รวมตัวชี้อัจฉริยะ) - ดำเนินการแต่ละที่จำเป็น new ในตัวสร้างภายในตัวของมันเอง ซ้อน ลอง/จับ - จากนั้นสิ่งที่จับได้สามารถล้างความพยายามก่อนหน้านี้ทั้งหมด จากนั้นโยนบางสิ่ง - person DMA57361; 05.07.2010
comment
@ DMA57361: ทางออกที่ดีที่สุดคือการใช้ตัวจัดการอัจฉริยะสำหรับแต่ละทรัพยากร: ตัวชี้อัจฉริยะสำหรับวัตถุใหม่ std::vector สำหรับอาร์เรย์ที่จัดสรรแบบไดนามิก ฯลฯ ... การใช้ try/catch เพื่อจัดการการเปิดตัวทรัพยากรนั้นคล้ายกับการใช้ ค้อนสำหรับทำศัลยกรรม อาจจะได้ผล แต่ซับซ้อนโดยไม่จำเป็น และไม่น่าจะได้ผลนานขนาดนั้น (การบำรุงรักษา...) - person Matthieu M.; 06.07.2010
comment
@Matthiew M - โอ้ฉันเห็นด้วยอย่างยิ่ง ฉันแค่พยายามทำความเข้าใจให้มากขึ้นว่าสิ่งที่ดูเหมือนจะเป็น (หรือถูกต้องกว่านั้น คือ) เป็นพื้นที่ที่ค่อนข้างซับซ้อน และเมื่อถึงจุดหนึ่ง ชั้นเรียนบางชั้นจะต้องแก้ไขปัญหานี้ใช่ไหม พอยน์เตอร์อัจฉริยะและสิ่งที่คล้ายกันที่เราใช้ต้องจัดการปัญหานี้ เพราะพวกเขาไม่สามารถใช้โครงสร้างเหล่านี้ได้ด้วยตัวเอง - แต่ฉันเดาว่านี่ส่วนใหญ่จะอยู่ภายในแกนกลางของไลบรารี ซึ่งพวกเราส่วนใหญ่ไม่จำเป็นต้องกังวลจริงๆ เกี่ยวกับมัน... - person DMA57361; 06.07.2010
comment
@ DMA57361:*...บางคลาสกำลังจะแก้ไขปัญหานี้...* ไม่จริง ตัวชี้อัจฉริยะนั้นค่อนข้างง่ายและไม่จำเป็นต้องจัดการเรื่องนี้จริงๆ ทรัพยากรถูกสร้างขึ้นจากภายนอก หากการสร้างนั้นล้มเหลว ทรัพยากรนั้นจะถูกล้างโดยอัตโนมัติ จากนั้นจะถูกส่งต่อไปยังตัวชี้อัจฉริยะ และตัวชี้อัจฉริยะจะคัดลอกเฉพาะตัวชี้เท่านั้น ไม่สามารถโยนได้ หากในเวลาต่อมาอะไรพ่นไม่เป็นปัญหาจริงๆ คลาสที่ใช้ RAII ได้รับการสร้างขึ้นอย่างสมบูรณ์แล้วสามารถถูกทำลายได้ ตัวทำลายล้างสามารถทำการล้างข้อมูลด้วยวิธีที่ง่ายที่สุดที่เป็นไปได้ - person David Rodríguez - dribeas; 06.07.2010

คุณไม่สามารถดำเนินการใดๆ ในตัวจัดการ function-try-block ได้ ยกเว้นการแปลข้อยกเว้นหนึ่งไปเป็นอีกข้อยกเว้นหนึ่ง คุณไม่สามารถป้องกันการโยนข้อยกเว้นได้ คุณไม่สามารถทำอะไรกับสมาชิกชั้นเรียนได้ ไม่ คุณไม่สามารถทำ delete c ใน function-try-block ของ Constructor ได้

คลาสฐานที่สร้างขึ้นอย่างสมบูรณ์และสมาชิกของวัตถุจะต้องถูกทำลายก่อนที่จะเข้าสู่ตัวจัดการของ function-try-block ของตัวสร้างหรือตัวทำลายสำหรับวัตถุนั้น

และ

ข้อยกเว้นที่ได้รับการจัดการในปัจจุบันจะถูกสร้างใหม่หากการควบคุมถึงจุดสิ้นสุดของตัวจัดการของฟังก์ชันลองบล็อกของตัวสร้างหรือตัวทำลาย มิฉะนั้น ฟังก์ชันจะกลับมาเมื่อการควบคุมถึงจุดสิ้นสุดของตัวจัดการสำหรับ function-try-block (6.6.3) การไหลออกจากจุดสิ้นสุดของ function-try-block เทียบเท่ากับการส่งคืนโดยไม่มีค่า ซึ่งส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนดไว้ในฟังก์ชันส่งกลับค่า

(15.3 [ยกเว้น.handle] จากมาตรฐาน C++)

person atzz    schedule 05.07.2010
comment
โดยพื้นฐานแล้ว วัตถุจะถูกทำลาย (อย่างมีประสิทธิภาพ) โดยเป็นส่วนหนึ่งของการผ่อนคลายที่เกิดขึ้นก่อนที่ catch จะเริ่มทำงาน ดังนั้นใน catch วัตถุปัจจุบันไม่มีอยู่ คุณไม่สามารถพยายามล้างข้อมูลบนวัตถุที่ไม่มีอยู่ได้ - person DMA57361; 05.07.2010
comment
@ DMA57361 - ใช่ ความเข้าใจของฉันคือ function-try-block ถูกเพิ่มเข้าไปในภาษาโดยมีวัตถุประสงค์เพื่อการแปลข้อยกเว้นในตัวสร้างเท่านั้น เพื่ออำนวยความสะดวกในการใช้งานร่วมกันของคลาสจากไลบรารีอิสระ นอกจากนี้คุณยังสามารถใส่บันทึกที่นั่นได้ ก็ประมาณนั้นครับ... - person atzz; 05.07.2010

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

คุณพูดถูกว่าหน่วยความจำรั่วจะเกิดขึ้นก็ต่อเมื่อ d ทำให้เกิดข้อยกเว้น และในกรณีนั้น catch ภายนอกจำเป็นต้องล้างข้อมูล

หาก new C พ่นออกมาเอง แสดงว่าวัตถุนั้นไม่เคยถูกสร้างขึ้น ดังนั้นคุณจึงไม่จำเป็นต้องกังวลเกี่ยวกับการลบมัน

person casablanca    schedule 05.07.2010
comment
ข้อเสนอแนะใด ๆ เกี่ยวกับวิธีที่สิ่งที่จับได้สามารถทำความสะอาดได้อย่างปลอดภัย? ฉันรู้สึกว่ามันทำไม่ได้ และคุณอาจพบว่าหน่วยความจำรั่วหรือเสี่ยงต่อการลบพอยน์เตอร์ที่ไม่ได้เตรียมใช้งาน - person DMA57361; 05.07.2010
comment
ในทางหนึ่ง คุณพูดถูก มันจะรั่วไหลไปสู่สถานการณ์ที่น่าอึดอัดใจ และคุณไม่สามารถรับประกันวิธีแก้ปัญหาที่สะอาดได้เสมอไป หาก b, c และ d มีข้อยกเว้นที่แตกต่างกัน คุณจะสามารถทราบได้ว่าความล้มเหลวเกิดขึ้นที่ใดและล้างข้อมูลตามนั้น - person casablanca; 05.07.2010