天天看點

C++中複制構造函數與重載指派操作符的深入分析

    在C++中複制控制是一個比較重要的話題,主要包括複制構造函數、重載指派操作符、析構函數這三部分,這三個函數是一緻的,如果需要手動定義了其中了一個,那麼另外的兩個也需要定義,通常在存在指針或者前期相關操作的情況下,都需要手動的定義。複制構造函數與重載指派操作符實作的大題相同,如果沒有手動的實作,那麼編譯器會自動生成一個,而且這兩個函數的參數也是一緻的,是不能夠改變的,這是為什麼呢?這是值得我們去分析和推敲的。析構函數相比前面的兩個存在一個巨大的差别,就是無論我們是否定義這個函數,編譯器都會自動生成一個析構函數。析構函數我認為主要是完成對象的釋放操作,我就不去仔細的分析啦。

    我們現在着重來分析一下為什麼複制構造函數與重載指派操作符在沒有定義的情況下,編譯器會為我們生成一個,這說明這兩個函數是一個類必不可少的部分。由此可知如果一個類沒有定義任何的東西,編譯器也會幫助我們生成下面的4個函數:1、一個構造函數,也就是所謂的類名比如classname(),這是在沒有定義構造函數時,編譯器會自動生成的。2、析構函數,這個前面已經提到了。3、複制構造函數。4、重載指派操作符。

    為什麼複制構造函數和重載指派操作符如此重要呢?

    首先介紹一下複制構造函數與重載指派操作符的聲明形式,這兩個函數的參數是固定的,是不能改變的。假設存在一個類Base。那麼它的這兩個函數聲明分别如下所示:

點選(此處)折疊或打開

class Base

{

public:

    //構造函數

    Base();

    //複制構造函數

    Base(const Base &);

    //重載指派操作符

    Base &operator=(const Base &);

    //析構函數

    ~Base();

    ...

private:

};

    面的形式可以知道,複制構造函數與重載指派操作符的參數是一緻的,都是該類的const引用類型。為什麼不能重載呢?這些都是存在的問題。我覺得要搞清楚這些問題,首先我們就不能隻能片面的去了解,而是應該對C++的面向對象程式設計有了一個較好的了解以後再來分析這個問題。

    麼是引用,很多書上都會說是為了避免複制,這是其中的一個原因,如果說隻是這個原因,完全可以隻用指針就可以了,對于系統而言,不在乎多幾個指針的存儲空間。

首先說明一下在C++中的繼承問題,一般而言我們的繼承主要是指公共繼承,也就說如下的派生類定義所示:

class Derived : public Base

 public:

   ...

}

    公共繼承而言,基類的公共部分成為了派生類的公共部分,私有部分成為了派生類的私有部分。也就是說派生類中包含基類的部分。也就是說在實作派生類的過程中,基類的公用接口會被派生類直接繼承。而基類的私有成員也作為派生類的私有成員。但是對于其他的繼承類型,我就不去讨論啦。這時派生類的成員可以通路到基類的私有成員。也就是說相當于派生類是在基類的基礎上增加了自己的特色。

    需要介紹一個問題就是采用派生類對象的引用初始化基類的引用。多态性的動态綁定中存在兩個條件:1、必須是virtual函數(虛函數),2、必須是通過基類的引用或者基類的指針進行成員虛函數的調用。

隻有上面兩個條件滿足才能發生動态調用問題。

    由于派生類中存在基類的成員,也就相當于一個派生類對象中包含了一個基類對象(我不知道這樣是否合理),是以可以采用一個基類引用來綁定一個派生類對象。引用實質上是針對一塊記憶體區域,引用是一個标号,是這塊記憶體區域的一個名字,一個引用與一塊記憶體區域綁定,因為派生對象中存在基類部分,可以認為派生對象的區域中存在基類對象,這時可用基類的引用來表明這塊記憶體區域,即采用一個基類的别名來表示(綁定)這段記憶體區域,派生對象的位址(這段記憶體)以及内容都沒有發生改變,也沒有重制創造出一個新的對象,基類的引用還是指向這個派生對象。對于指針的分析方式相似。是以可以采用基類的引用綁定派生類對象。

    但是如何實作派生類對象到基類的轉換呢?

    這時候的轉換與前面的綁定存在很大的差别,因為這是重新配置設定一個基類對象,而不再是引用問題,不再是綁定問題,是依據一個派生類對象生成一個新的基類對象。因為派生類對象中存在一個基類對象基本的資訊,完全可以生成一個基類對象,完全将此過程看作是一個初始化或者指派的問題。也就是采用派生類建立一個新的對象或者指派一個對象。

    從上面的分析我們可以采用下面的形式來實作:

Base(const Derived &);

Base &operator=(const Derived &);

    是在基類函數中采用構造函數基于派生類來重載一系列的構造函數,但是這也存在一個問題,如果存在很多派生類,這時候就要重載很多構造函數,這肯定不是我們需要的。

    這時候是否發現與前面的問題關聯起來了?

    這時候我們發現對于一個類而言,為什麼複制構造函數和重載指派操作符這麼重要了。因為這兩個函數都是接受一個基類的引用,根據前面的分析我們知道一個基類引用完全可以綁定一個派生類的對象,而派生類對象中又包含了一個基類對象的基本資訊。我們能夠實作一個從一個派生對象到基類的構造過程。

    我們用一個基類引用綁定一個派生對象,然後采用基類引用對基類成員進行通路,完成了一個基類對象基本要素的填充操作,相當于完成了基類對象的建立,也就是構造問題。這樣也就能完成由派生類對象到基類對象的構造過程。

    至于為什麼是一個const的引用,我認為主要是因為不去修改派生類對象和擴大能夠接受的參數情況,由const引用可以綁定不同類型的對象特性,說明const引用會擴大接受實參的能力。

現在我可以總結起來說了,因為在複制構造函數中,C++中的基類引用可以綁定一個派生類的對象,如果在允許通路的情況下,采用基類引用可以通路基類的成員以及派生類的其他成員,采用引用可以複制派生類對象中基類成員的值到新建立的基類成員中,完成一個基類成員資料的填充操作,這時候一個完整的基類對象就建立完成了。

    重載指派操作符則是發生在使用一個派生對象來指派一個基類對象時,這時候也是const基類引用綁定一個派生類對象,然後複制對應的基類成員到基類對象對于的成員中,完成一個基類對象成員的更新操作。

    來說,複制構造函數不僅僅實作了同類型之間的初始化操作,同時也完成了采用一個派生類對象初始化一個基類對象的操作,重載指派操作符實作了同類型之間的指派操作,也完成了采用派生類對象指派基類對象的操作。如果沒有這兩個函數的存在,也就不能完成派生類到基類的指派和初始化操作。這也是為什麼一定會存在這兩個函數的原因。

繼續閱讀