Pertanyaan Pengecualian C ++ saat memutar ulang pengecualian asli

117

Akankah append () berikut dalam tangkapan menyebabkan pengecualian yang ditarik kembali untuk melihat efek append () dipanggil?

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

Demikian pula, jika saya menulis ulang dengan cara ini, apakah bit slicing akan terjadi jika pengecualian sebenarnya diturunkan oleh 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?
}
WilliamKF
sumber

Jawaban:

150

Dalam kedua kasus, karena Anda menangkap dengan referensi, Anda secara efektif mengubah status objek pengecualian asli (yang dapat Anda anggap sebagai berada di lokasi memori ajaib yang akan tetap valid selama pembatalan berikutnya - 0x98e7058dalam contoh di bawah). Namun,

  1. Dalam kasus pertama, karena Anda menelusuri ulang throw;(yang, tidak seperti throw err;, mempertahankan objek pengecualian asli, dengan modifikasi Anda, dalam kata "lokasi magis" di 0x98e7058) akan mencerminkan panggilan ke append ()
  2. Dalam kasus kedua, karena Anda membuang sesuatu secara eksplisit, salinan dari errakan dibuat kemudian dilemparkan lagi (di "lokasi magis" yang berbeda 0x98e70b0- karena semua yang diketahui oleh kompilator errdapat menjadi objek di tumpukan yang akan dilepas, seperti esebelumnya di 0xbfbce430, bukan di "lokasi magis" di 0x98e7058), jadi Anda akan kehilangan data khusus kelas turunan selama pembuatan salinan instance kelas dasar.

Program sederhana untuk menggambarkan apa yang terjadi:

#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;
}

Hasil:

  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

Lihat juga:

vladr
sumber
24

Pertanyaan ini agak kuno dan memiliki jawaban yang sesuai dengan waktu yang ditanyakan. Namun, saya hanya ingin menambahkan catatan tentang cara melakukan penanganan pengecualian yang tepat sejak C ++ 11 dan saya yakin ini sangat sesuai dengan apa yang Anda coba capai dengan fungsi append:

Gunakan std::nested_exceptiondanstd::throw_with_nested

Ini dijelaskan di StackOverflow di sini dan di sini , bagaimana Anda bisa mendapatkan pelacakan balik pada pengecualian Anda di dalam kode Anda tanpa perlu debugger atau logging yang rumit, cukup dengan menulis penangan pengecualian yang tepat yang akan menampilkan kembali pengecualian bertingkat.

Karena Anda dapat melakukan ini dengan kelas pengecualian apa pun, Anda dapat menambahkan banyak informasi ke lacak balik seperti itu! Anda juga dapat melihat MWE saya di GitHub , di mana backtrace akan terlihat seperti ini:

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"
GPMueller
sumber
8

Ya, melempar ulang akan memunculkan kembali objek pengecualian asli, yang telah Anda modifikasi dengan referensi. Anda juga dapat menangkap referensi kelas dasar, memodifikasinya, dan masih dapat menampilkan kembali jenis pengecualian turunan asli dengan throw;.

Tronic
sumber
1

untuk pertanyaan pertama, ya.

tapi untuk kedua, lihat jawaban Vlad. Anda perlu mendesain objek pengecualian Anda dengan hati-hati untuk menangani copy ctor. menurut konvensi, kelas dasar tidak mengenali anaknya sehingga Anda kemungkinan besar akan kehilangan data tambahan yang dibawa oleh kelas turunan.

YeenFei
sumber