คำถามข้อยกเว้น C ++ เกี่ยวกับการเปลี่ยนข้อยกเว้นเดิม


118

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

try {
  mayThrowMyErr();
} catch (myErr &err) {
  err.append("Add to my message here");
  throw; // Does the rethrow exception reflect the call to append()?
}

ในทำนองเดียวกันถ้าฉันเขียนใหม่ด้วยวิธีนี้การแบ่งบิตจะเกิดขึ้นหาก myErr มีข้อยกเว้นจริงหรือไม่

try {
  mayThrowObjectDerivedFromMyErr();
} catch (myErr &err) {
  err.append("Add to my message's base class here");
  throw err; // Do I lose the derived class exception and only get myErr?
}

คำตอบ:


151

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

  1. ในกรณีแรกเนื่องจากคุณสร้างใหม่ด้วยthrow;(ซึ่งแตกต่างจากการthrow err;รักษาวัตถุข้อยกเว้นดั้งเดิมด้วยการปรับเปลี่ยนของคุณใน "สถานที่วิเศษ" ที่กล่าวว่า0x98e7058) จะแสดงถึงการเรียกให้ผนวก ()
  2. ในกรณีที่สองเนื่องจากคุณโยนบางสิ่งอย่างโจ่งแจ้งสำเนาของerrจะถูกสร้างขึ้นแล้วโยนใหม่ (ที่ "สถานที่วิเศษ" ที่แตกต่างกัน0x98e70b0- เพราะสำหรับคอมไพเลอร์ทั้งหมดรู้ว่าerrอาจเป็นวัตถุบนสแต็กที่กำลังจะคลายออกเช่นeเดิม ที่0xbfbce430ไม่ใช่ใน "สถานที่วิเศษ" ที่0x98e7058) ดังนั้นคุณจะสูญเสียข้อมูลเฉพาะคลาสที่ได้รับระหว่างการสร้างสำเนาของอินสแตนซ์คลาสพื้นฐาน

โปรแกรมง่ายๆเพื่อแสดงสิ่งที่เกิดขึ้น:

#include <stdio.h>

struct MyErr {
  MyErr() {
    printf("  Base default constructor, this=%p\n", this);
  }
  MyErr(const MyErr& other) {
    printf("  Base copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErr() {
    printf("  Base destructor, this=%p\n", this);
  }
};

struct MyErrDerived : public MyErr {
  MyErrDerived() {
    printf("  Derived default constructor, this=%p\n", this);
  }
  MyErrDerived(const MyErrDerived& other) {
    printf("  Derived copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErrDerived() {
    printf("  Derived destructor, this=%p\n", this);
  }
};

int main() {
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("A Inner catch, &err=%p\n", &err);
      throw;
    }
  } catch (MyErr& err) {
    printf("A Outer catch, &err=%p\n", &err);
  }
  printf("---\n");
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("B Inner catch, &err=%p\n", &err);
      throw err;
    }
  } catch (MyErr& err) {
    printf("B Outer catch, &err=%p\n", &err);
  }
  return 0;
}

ผลลัพธ์:

  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
A Inner catch, &err=0x98e7058
A Outer catch, &err=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
---
  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
B Inner catch, &err=0x98e7058
  Base copy-constructor, this=0x98e70b0 from that=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
B Outer catch, &err=0x98e70b0
  Base destructor, this=0x98e70b0

ดูเพิ่มเติมที่:


25

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

ใช้std::nested_exceptionและstd::throw_with_nested

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

เนื่องจากคุณสามารถทำสิ่งนี้กับคลาสข้อยกเว้นที่ได้รับใด ๆ คุณจึงสามารถเพิ่มข้อมูลจำนวนมากลงใน backtrace ดังกล่าวได้! คุณสามารถดูMWEของฉันบน GitHubซึ่ง backtrace จะมีลักษณะดังนี้:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

8

ใช่การย้อนกลับใหม่จะทำให้อ็อบเจ็กต์ข้อยกเว้นดั้งเดิมซึ่งคุณได้แก้ไขโดยการอ้างอิง นอกจากนี้คุณยังสามารถจับอ้างอิงชั้นฐานแก้ไขโดยมันและยังคงสามารถที่จะ rethrow throw;เดิมประเภทยกเว้นที่ได้มาโดย


1

สำหรับคำถามแรกใช่

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

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