วิธีจัดการกรณีความล้มเหลวในตัวสร้างคลาส C ++


21

ฉันมีคลาส CPP ซึ่งตัวสร้างทำการดำเนินการบางอย่าง การดำเนินการบางอย่างอาจล้มเหลว ฉันรู้ว่าผู้สร้างไม่คืนสิ่งใด

คำถามของฉันคือ

  1. มันอนุญาตให้ดำเนินการบางอย่างอื่น ๆ ที่เริ่มต้นสมาชิกในนวกรรมิก?

  2. เป็นไปได้ไหมที่จะบอกฟังก์ชั่นการโทรว่าการทำงานบางอย่างใน Constructor ล้มเหลว?

  3. ฉันจะnew ClassName()คืนค่า NULL ได้ไหมหากมีข้อผิดพลาดเกิดขึ้นในตัวสร้าง?


22
คุณสามารถโยนข้อยกเว้นจากภายในตัวสร้าง มันเป็นรูปแบบที่ถูกต้องสมบูรณ์
Andy

1
คุณน่าจะลองดูรูปแบบการสร้างสรรค์ของ GoFบ้าง ฉันแนะนำรูปแบบโรงงาน
SpaceTrucker

2
ตัวอย่างทั่วไปของ # 1 คือการตรวจสอบข้อมูล IE ถ้าคุณมีคลาสที่Squareมี Constructor ที่ใช้พารามิเตอร์หนึ่งตัวความยาวของด้านคุณต้องการตรวจสอบว่าค่านั้นมากกว่า 0 หรือไม่
David พูดว่า Reinstate Monica

1
สำหรับคำถามแรกให้ฉันเตือนคุณว่าฟังก์ชั่นเสมือนจริงสามารถทำงานได้โดยไม่ตั้งใจในตัวสร้าง เช่นเดียวกันกับ deconstructors ระวังโทรเช่นนี้

1
# 3 - ทำไมคุณต้องการคืนค่า NULL ข้อดีอย่างหนึ่งของ OO คือไม่ต้องตรวจสอบค่าส่งคืน เพียงแค่จับ () ข้อยกเว้นที่เหมาะสม
MrWonderful

คำตอบ:


42
  1. ใช่แม้ว่ามาตรฐานการเข้ารหัสบางอย่างอาจห้ามมัน

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

  3. เลขที่


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

@DavidPacker ตกลงเห็นด้วยที่นี่: stackoverflow.com/questions/77639/…แต่แนวทางการเข้ารหัสบางข้อห้ามข้อยกเว้นซึ่งเป็นปัญหาสำหรับตัวสร้าง
เซบาสเตียนเรดล

อย่างใดฉันได้ให้ upvote สำหรับคำตอบนั้นเซบาสเตียน น่าสนใจ : D
Andy

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

1
สำหรับ # 1 RAII เป็นตัวอย่างทั่วไปที่อาจต้องใช้การสร้างมากขึ้นในตัวสร้าง
Eric

20

คุณสามารถสร้างวิธีการคงที่ที่ทำการคำนวณและส่งคืนวัตถุในกรณีของความสำเร็จหรือไม่กรณีของความล้มเหลว

ทั้งนี้ขึ้นอยู่กับวิธีการสร้างวัตถุนี้มันอาจจะดีกว่าที่จะสร้างวัตถุอื่นที่อนุญาตให้สร้างวัตถุในวิธีที่ไม่คงที่

การเรียกนวกรรมิกทางอ้อมมักเรียกกันว่า "โรงงาน"

สิ่งนี้จะช่วยให้คุณส่งคืน null-object ซึ่งอาจเป็นวิธีที่ดีกว่าการส่งคืน null


ขอบคุณ @ null! น่าเสียดายที่ไม่สามารถตอบสองคำตอบได้ที่นี่ :( มิฉะนั้นฉันก็จะยอมรับคำตอบนี้ด้วย !! ขอบคุณอีกครั้ง!
MayurK

@MayurK ไม่ต้องกังวลคำตอบที่ได้รับการยอมรับไม่ได้ที่จะทำเครื่องหมายตอบที่ถูกต้อง แต่อย่างหนึ่งที่ทำงานให้กับคุณ
null

3
@null: ใน C ++ คุณไม่สามารถกลับมาNULLได้ ตัวอย่างเช่นในความint foo() { return NULLเป็นจริงคุณจะกลับ0(ศูนย์) วัตถุจำนวนเต็ม ในstd::string foo() { return NULL; }คุณจะโทรหาโดยไม่ตั้งใจstd::string::string((const char*)NULL)ซึ่งเป็นพฤติกรรมที่ไม่ได้กำหนด (NULL ไม่ได้ชี้ไปที่ \ สิ้นสุดสาย \ 0)
MSalters

3
std :: เป็นทางเลือกอาจจะอยู่ห่างออกไป แต่คุณสามารถใช้ boost :: เป็นทางเลือกได้เสมอถ้าคุณต้องการที่จะไปทางนั้น
ฌอนเบอร์ตัน

1
@Vld: ใน C ++ วัตถุไม่ได้ถูก จำกัด ประเภทคลาส intและกับการเขียนโปรแกรมทั่วไปก็ไม่ใช่เรื่องแปลกที่จะจบลงด้วยโรงงาน เช่นstd::allocator<int>เป็นโรงงานที่สมบูรณ์แบบ
MSalters

5

@SebastianRedl ได้ให้คำตอบที่ตรงไปตรงมาแล้ว แต่คำอธิบายพิเศษบางอย่างอาจมีประโยชน์

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


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

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

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

try .. catchถ้าโยนยกเว้นเกิดขึ้นภายในตัวสร้างสิ่งที่ตอบสนองความต้องการส่วนสร้างขึ้นมาเพื่อทำความสะอาดได้อย่างชัดเจนขึ้นโดยทั่วไปใน

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

classname (args) : base1 (args), member2 (args), member3 (args)
{
}

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

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

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

และแน่นอนว่าคุณสามารถสรุปได้ในmake_whatever ()ฟังก์ชั่นดังนั้นผู้โทรของฟังก์ชันนั้นไม่จำเป็นต้องดูอินสแตนซ์ของคลาสที่ไม่เปลี่ยนแปลง


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

ดังนั้นคอนสตรัค "do-the-low" สามารถเป็นไพรเวตและฟังก์ชัน "make_whething ()" สามารถเป็นคอนสตรัคเตอร์อื่นที่เรียกไพรเวตได้
Ben Voigt

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

1
@Eric - ใช่มันเป็นแบบมาตรฐานอย่างแน่นอน - แบบมาตรฐานซึ่งโดยทั่วไปเรียกว่า RAII มันไม่ใช่แค่ฉันที่ยืดความหมาย - แม้แต่ Stroustrup ในการพูดคุยบางอย่าง ใช่ RAII เป็นเรื่องเกี่ยวกับการเชื่อมโยงวงจรชีวิตของทรัพยากรกับวัฏจักรวัตถุซึ่งเป็นแบบจำลองทางจิตที่เป็นเจ้าของ
Steve314

1
@Eric - ลบคำตอบก่อนหน้าเพราะถูกอธิบายอย่างไม่ดี อย่างไรก็ตามวัตถุเองเป็นทรัพยากรที่สามารถเป็นเจ้าของได้ ทุกอย่างควรมีเจ้าของในห่วงโซ่จนถึงmainฟังก์ชันหรือตัวแปรคงที่ / ทั่วโลก วัตถุจัดสรรใช้new, ไม่ได้เป็นเจ้าของจนกว่าคุณจะกำหนดความรับผิดชอบ แต่ตัวชี้สมาร์ทเป็นเจ้าของวัตถุกองจัดสรรพวกเขาอ้างอิงและภาชนะบรรจุที่เป็นเจ้าของโครงสร้างข้อมูลของพวกเขา เจ้าของสามารถเลือกที่จะลบ แต่เนิ่น ๆ เจ้าของ destructor เป็นผู้รับผิดชอบในที่สุด
Steve314
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.