簡體   English   中英

派生類構造函數中的異常

[英]Exception in derived class constructor

我在處理派生類中的構造函數異常時遇到一些問題。 當派生類構造函數引發錯誤,但父類已分配了一些對象時。 父類的析構函數會被調用嗎?

例:

class A
{
  A() { /* Allocate some stuff */ };

  virtual ~A() { /* Deallocate stuff */ };
};

class B : public A
{
  B() 
  { 
    /* Do some allocations */
    ...

    /* Something bad happened here */
    if (somethingBadHappened) 
    {
      /* Deallocate B Stuff */
      ...

      /* Throws the error */
      throw Error; /* Will A destructor be called? I know B destructor won't */
    };

  };

  ~B() { /* Deallocate B Stuff */ };
}

我想知道執行以下操作是否是一個好主意:

B()
{ 
  /* Do some allocations */
  ...

  /* Something bad happened here */
  if (somethingBadHappened) 
  {
    /* Deallocate B Stuff */
    this->~B();

    /* Throws the error */
    throw Error; /* Will A destructor be called? I know B destructor won't */
  };
};

如果沒有,那么做這些事情的一種體面的方法是什么?

異常將導致堆棧展開到正確捕獲異常的位置。 這意味着在拋出異常之前在范圍內創建的任何對象都將被破壞,包括本示例中的基類對象。

嘗試這個:

#include <iostream>

class A
{
public:
  A() { std::cout << "A::A()\n";}
  ~A() {std::cout << "A::~A()\n";}
};

class B : public A
{
public:
   B()
   {
      std::cout << "B::B()\n";
      throw 'c';
   }

   // note: a popular point of confusion -- 
   //   in this example, this object's destructor
   //   WILL NOT BE CALLED!
   ~B()
   {
      std::cout << "B::~B()\n";
   }
};


int main()
{
   try
   {
      B b;
   }

   catch(...)
   {
      std::cout << "Fin\n";
   }
   return 0;
}

輸出應為:(注意未調用B::~B()

A::A()
B::B()
A::~A()
Fin

只要您不試圖釋放尚未分配的資源,就可以按照問題中的說明手動調用析構函數。 最好將這些資源包裝在某種類型的RAII容器中( std::auto_ptrboost::shared_ptr等),以避免調用析構函數的必要性。

Mooing Duck很好地說明了在構造函數中引發異常時堆棧展開的工作方式:

構造函數異常期間的堆棧展開

還沒有完全考慮到這一點,但是可以考慮在try / catch塊中創建對象。 如果構造函數引發異常,請delete該對象(如果該對象是使用new創建的)。

try
{
    B* b = new B();
}
catch
{
    delete b;
    //log error
}

如果不使用newb分配內存,則無需在catch塊中調用delete。

確保您的B析構函數不會對從未創建的對象調用delete 我建議在進行可能導致異常的任何操作之前,在構造函數中設置所有指向等於0的對象的指針的成員。 這樣,如果調用了析構函數,則delete它們是安全的。

您在問題第二部分中嘗試編寫干凈的構造函數B::B()失敗嘗試凸顯了在一個類中承擔過多責任的設計的尷尬。 如果僅使用單一職責組件,則通常可以完全不編寫任何顯式錯誤檢查,而讓異常處理機制以遞歸方式工作。

考慮一下:

B::B()
{
  try { this->p1 = get_dangerous_pointer(); }
  catch(...) { throw; } // OK

  try { this->p2 = suicidal_function(); }
  catch(...) {
    clean_up(p1);
    throw;
  }

  try { this->p3 = get_monstrous_amounts_of_memory(); }
  catch(...)
  {
    clean_up(p2);
    clean_up(p1);
    throw;
  }
}

如您所見,為只承擔三種不同職責的類編寫正確的構造函數是一場噩夢。

正確的解決方案是使每個資源都歸包裝類所有,而包裝類的唯一責任是擁有該資源,即使面對最特殊的異常,清理也會自動進行。

還要注意,從任何構造函數中調用成員函數時都必須格外小心 對象的生命周期直到構造函數完成后才開始,因此當您在構造函數中時,您正在使用“正在構造的對象”-有點像對您自己進行的心臟直視手術。 特別是,您不得調用析構函數,因為只允許破壞完整的對象。

最好的主意是在構造中捕獲異常,然后將對象置於會產生錯誤的狀態(例如,對象讀取文件,在構造函數中打開文件失敗,然后讀取將不起作用)。

只要保持對象一致即可。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM