當心編譯器生成的隱含成員函式

2021-04-30 05:01:14 字數 3294 閱讀 5198

當心編譯器生成的隱含成員函式 ***

「聽說最近新開了家超市?」

「是啊,我去過了,什麼都沒有!」

「真的?什麼都沒有?」

「我騙你幹什麼!只有些牙膏、牙刷、洗衣粉……真的什麼都沒有。」

習以為常的東西總是容易被忽略。在生活中我們可以對這些經常見到的東西視而不見,但在**中千萬不要這樣作。

問題:下面這個類有幾個成員函式 ?

class x

int value;

「這麼簡單的問題還問!我當然知道,編譯器 會為沒有建構函式的類生成乙個預設建構函式,為沒有拷貝建構函式的類生成乙個拷貝建構函式,為沒有析構函式的類生成乙個析構函式,為沒有拷貝賦值操作符的類生成乙個拷貝賦值操作符,所以它有四個隱含成員函式。」

是的,回答是正確的,但僅限於知道是不行的,必須把它牢牢的記住,直到你一看到這個定義眼前就能浮現出下面的定義:

class x

int value;

public:

x();

x(const x&);

x& operator = (const x&);

~x();

比如我們要實現乙個「智慧型指標」(什麼型別的?什麼型別都行,這與本題無關),這,通常都會這樣寫:

template

smart_ptr...

現在我們要為它寫乙個拷貝建構函式和乙個拷貝賦值操作符……等一下。

我們這個智慧型指標是為了在某一應用範圍內代替指標的,我們應該讓它支援指標的一些特性,如自動型別轉換的初始化(拷貝構造)和賦值。而類模板和 真正的指標是不一樣的,即使乙個derived*是乙個base*,但乙個smart_ptr卻不是乙個 smart_ptr,從smart_ptr到smart_ptr的轉換 不是編譯器自動支援的,我們必須為它寫一些**來做這件事。該怎麼做呢?很簡單:

template

smart_ptr

t* ptr_data;

public:

t* get_ptr();

template

smart_ptr(const smart_ptr& source )

ptr_data = source.get_ptr();

//其它處理

template

smart_ptr& operator = (const smart_ptr& source)

ptr_data = source.get_ptr();

//其它處理

這樣當我們用smart_ptr初始化smart_ptr的時候,編譯器會自動為 smart_ptr類生成乙個引數為const smart_ptr& 的建構函式,用smart_ptr::get_ptr()的返回值(型別為derived*)初始化 smart_ptr::ptr_data(型別為base*),而編譯器會在這裡進行型別檢查,其檢查結果和直接使用指標型別時 一樣。現在你松了一口氣——一切ok,大功告成……可是你錯了!

當你寫下這樣一段**時:

smart_ptra;

smart_ptrb(a);

smart_ptrc;

c = a;

你會發現,它並沒有呼叫 你 的建構函式的賦值操作符。什麼原因呢?原因很簡單:當你沒有寫拷貝建構函式時,編譯器會為你生成乙個隱含的拷貝建構函式。而拷貝建構函式的定義是「類x的 拷貝建構函式是指第乙個引數為const x&、x&、volatile x&或const volatile x&,並且沒有其它引數或其它引數都有預設值的非模板形式的建構函式」,因此上templatesmart_ptr(const smart_ptr& source)這個建構函式並不是拷貝建構函式,由於這個原因,編譯器會為你生成乙個隱含的拷貝建構函式。事實上你的類是這個樣子的:

template

smart_ptr

t* ptr_data;

public:

t* get_ptr();

//以下是編譯器自動生成的三個成員函式

smart_ptr(const smart_ptr&);

smart_ptr& operator = (const smart_ptr&);

~smart_ptr();

template

smart_ptr(const smart_ptr& source)

ptr_data = source.get_ptr();

//其它處理

template

smart_ptr& operator = (const smart_ptr& source)

ptr_data = source.get_ptr();

//其它處理

現在我們看的很清楚了:由於smart_ptr(const smart_ptr&);這個函式的存在,在編譯**smart_ptrb(a);編譯器找到了乙個型別符合的非模板建構函式,它就會呼叫這個函式而不會去例項化你的模板形式的建構函式。同樣道理,你的模板形式的賦值操作符也 不是拷貝賦值操作符,編譯器同樣會為你生成乙個從而阻止了模板的例項化。

解決辦法很簡單,既然編譯器要呼叫拷貝建構函式和拷貝賦值操作符,而隱含的成員又不符合要求,我們就自己寫乙個來代替它們:

template

smart_ptr

t* ptr_data;

public:

t* get_ptr() const;//返回封裝的指標,可能還需要做一些附加操作,取決於你的智慧型指標的設計邏輯。

//自己的拷貝建構函式和拷貝賦值操作符:

smart_ptr(const smart_ptr&)

ptr_data = source.get_ptr();

//其它處理

smart_ptr& operator = (const smart_ptr&);

ptr_data = source.get_ptr();

//其它處理

template

smart_ptr(const smart_ptr& source)

ptr_data = source.get_ptr();

//其它處理

template

smart_ptr& operator = (const smart_ptr& source)

ptr_data = source.get_ptr();

//其它處理

事實上在《c++ stl 中文版》中auto_ptr的實現**中就是這樣實現的。

注:在nicolai m.josuttis編寫的《c++ 標準程式庫》(我讀的是侯捷和孟巖的譯本)中把這種建構函式稱為「模板形式的拷貝建構函式」(原譯文為「template形式的copy建構式」)是一種 不很嚴密的說法,容易讓人誤解。事實上這種模板形式的建構函式不屬於拷貝建構函式。

編譯器預設生成的函式

拷貝控制函式包括 拷貝建構函式 拷貝賦值函式 移動建構函式 移動賦值函式 析構函式。1.建構函式 如果我們沒有定義任何建構函式,編譯器會為我們生成乙個預設的建構函式。如果定義了,則沒有預設建構函式,即不能以class item來定義物件了。因此,不管有沒有定義建構函式,最好自己定義下預設建構函式。2...

C 編譯器生成的預設函式

c 編譯器生成的預設函式 話題引入 物件的賦值與複製是如何進行的?他們的區別是什麼?如果乙個空的自定義型別能否執行這些操作?物件賦值 通過 運算子過載 user a 10 b b a 物件複製 呼叫拷貝建構函式 user b user a b 或者 user a b 相當於user a b 也是呼叫...

拒絕編譯器自動生成的函式

編譯器自動的函式 有些場景中需要拒絕這些編譯器自動生成的函式,可以通過下列方法,定義uncopyable類並繼承它。拒絕使用編譯器生成的函式 class uncopyable uncopyable private 拒絕copy uncopyable const uncopyable 在這裡宣告,不用...