C++中的虛解構函式、純虛解構函式詳解

來源:互聯網
上載者:User

標籤:

C++中解構函式可以為純虛函數嗎?

眾所周知,在實現多態的過程中,一般將基類的解構函式設為virtual,以便在delete的時候能夠多態的鏈式調用。那麼解構函式是否可以設為純虛呢?

class CBase{    public:        CBase()       {            printf("CBase()\n");       }    virtual ~CBase() = 0; // 解構函式是純虛函數};

答案是可以,那麼這樣實現的目的是什麼呢?當然是避免執行個體化

但因為衍生類別不可能來實現基類的解構函式,所以基類解構函式雖然可以標為純虛,但是仍必須實現解構函式,否則衍生類別無法繼承,也無法編譯通過

下面詳細討論:

虛解構函式

我們知道,為了能夠正確的調用對象的解構函式,一般要求具有階層的頂級類定義其解構函式為虛函數。因為在delete一個抽象類別指標時候,必須要通過虛函數找到真正的解構函式。

如:

class Base  {      public:          Base(){}          virtual ~Base(){}  };  class Derived: public Base  {      public:          Derived(){};          ~Derived(){};  }  void foo()  {     Base *pb;     pb = new Derived;     delete pb;  } 

這是正確的用法,會發生動態綁定,它會先調用Derived的解構函式,然後是Base的解構函式

如果解構函式不加virtualdelete pb只會執行Base的解構函式,而不是真正的Derived解構函式。

因為不是virtual函數,所以調用的函數依賴於指向靜態類型,即Base

純虛解構函式

現在的問題是,我們想把Base做出抽象類別,不能直接構造對象,需要在其中定義一個純虛函數。如果其中沒有其他合適的函數,可以把解構函式定義為純虛的,即將前面的CObject定義改成:

class Base  {      public:          Base(){}          virtual ~Base()= 0;  }; 

可是,這段代碼不能通過編譯,通常是link錯誤,不能找到~Base()的引用(gcc的錯誤報表)。

error LNK2019: unresolved external symbol "public: virtual __thiscall Base::~Base(void)" (??1Base@@UAE@XZ) referenced in function "public: virtual __thiscall Derived::~Derived(void)" (??1Derived@@UAE@XZ)1>D:\C++\exercise\Debug\exercise.exe : fatal error LNK1120: 1 unresolved externals

這是因為,解構函式、建構函式和其他內建函式不一樣,在調用時,編譯器需要產生一個調用鏈。也就是,Derived的解構函式裡面隱含調用了Base的解構函式。而剛才的代碼中,缺少~Base()的函數體,當然會出現錯誤。

這裡面有一個誤區,有人認為,virtual f()=0這種純虛函數文法就是沒有定義體的語義。

其實,這是不對的。這種文法只是表明這個函數是一個純虛函數因此這個類變成了抽象類別,不能產生對象。我們完全可以為純虛函數指定函數體 。通常的純虛函數不需要函數體,是因為我們一般不會調用抽象類別的這個函數,只會調用衍生類別的對應函數。這樣,我們就有了一個純虛解構函式的函數體,上面的代碼需要改成:

class Base  {      public:          Base(){}          virtual ~Base() = 0; //pure virtual  };  Base::~Base()//function body  {  }  

從文法角度來說,不可以將上面的解構函式直接寫入類聲明中(內嵌函式的寫法)。這或許是一個不正交化的地方。但是這樣做的確顯得有點累贅

這個問題看起來有些學術化,因為一般我們完全可以在Base中找到一個更加適合的函數,通過將其定義為沒有實現體的純虛函數,而將整個類定義為抽象類別。但這種技術也有一些應用,如這個例子:

class Base  //abstract class  {      public:          virtual ~Base(){};//virtual, not pure          virtual void Hiberarchy() const = 0;//pure virtual  };  void Base::Hiberarchy() const //pure virtual also can have function body  {     std::cout <<"Base::Hiberarchy";  }  class Derived : public Base  {      public:          Derived(){}          virtual void Hiberarchy() const          {              Base::Hiberarchy();              std::cout <<"Derived::Hiberarchy";          }          virtual void foo(){}  };  int main()  {     Base* pb=new Derived();     pb->Hiberarchy();     pb->Base::Hiberarchy();     return 0;  }   

在這個例子中,我們試圖列印出類的繼承關係。在根基類中定義了虛函數Hiberarchy,然後在每個衍生類別中都重載此函數。我們再一次看到,由於想把Base做成個抽象類別,而這個類中沒有其他合適的方法成員可以定義為純虛的,我們還是只好將Hiberarchy定義為純虛的。(當然,完全可以定義~Base函數,這就和上面的討論一樣了。^_^)

另外,可以看到,在main中有兩種調用方法,第一種是普通的方式,進行動態連結,執行虛函數,得到結果"Derived::Hiberarchy";第二種是指定類的方式,就不再執行虛函數的動態連結過程了,結果是"Base::Hiberarchy"

通過上面的分析可以看出,定義純虛函數的真正目的是為了定義抽象類別,而並不是函數本身。與之對比,在java中,定義抽象類別的文法是 abstract class,也就是在類的一級作指定(當然虛函數還是也要加上abstract關鍵字)。是不是這種方式更好一些呢?在Stroustrup的《C++語言的設計與演化》中我找到這樣一段話:

“我選擇的是將個別的函數描述為純虛的方式,沒有採用將完整的類聲明定義為抽象的形式,這是因為純虛函數的概念更加靈活一些。我很看重能夠分階段定義類的能力;也就是說,我發現預先定義一些純虛函數,並把另外一些留給進一步的衍生類別去定義也是很有用的”。

我還沒有完全理解後一句話,我想從另外一個角度來闡述這個概念。那就是,在一個多層複雜類結構中,中介層次的類應該具體化一些抽象函數,但很可能並不是所有的。中間類沒必要知道是否具體化了所有的虛函數,以及其祖先已經具體化了哪些函數,只要關注自己的職責就可以了。也就是說,中間類沒必要知道自己是否是一個真正的抽象類別,設計者也就不用考慮是否需要在這個中間類的類層級上加上類似abstract的說明了。

當然,一個語言的設計有多種因素,好壞都是各個方面的。這隻是一個解釋而已。

最後,總結一下關於虛函數的一些常見問題:
  1. 虛函數是動態綁定的,也就是說,使用虛函數的指標和引用能夠正確找到實際類的對應函數,而不是執行定義類的函數。這是虛函數的準系統,就不再解釋了。

  2. 建構函式不能是虛函數。而且,在建構函式中調用虛函數,實際執行的是父類的對應函數,因為自己還沒有構造好, 多態是被disable的。

  3. 解構函式可以是虛函數,而且,在一個複雜類結構中,這往往是必須的

  4. 將一個函數定義為純虛函數,實際上是將這個類定義為抽象類別,不能執行個體化對象

  5. 純虛函數通常沒有定義體,但也完全可以擁有, 甚至可以顯示調用。

  6. 解構函式可以是純虛的,但純虛解構函式必須有定義體,因為解構函式的調用是在子類中隱含的

  7. 非純的虛函數必須有定義體,不然是一個錯誤。

  8. 衍生類別的override虛函數定義必須和父類完全一致(c++11中使用override進行編譯器檢查)。除了一個特例,如果父類中傳回值是一個指標或引用,子類override時可以返回這個指標(或引用)的派生。例如,在上面的例子中,在Base中定義了 virtual Base* clone(); 在Derived中可以定義為 virtual Derived* clone()。可以看到,這种放松對於Clone模式是非常有用的(也就是說override並不會檢查傳回值類型)。

C++中的虛解構函式、純虛解構函式詳解

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.