[cpp deep dive] 構(gòu)造函數(shù)與explicit

explicit
  • explicit關(guān)鍵字只能修飾構(gòu)造函數(shù),其余地方不起作用.
  • 單參數(shù)構(gòu)造函數(shù)會(huì)起一個(gè)作用,即會(huì)令該參數(shù)類型的對象在某些時(shí)候(如函數(shù)參數(shù))隱式轉(zhuǎn)換為本類類型。而加了explicit之后則會(huì)禁止這種轉(zhuǎn)換。
  • 另外該關(guān)鍵字只要放在聲明之前即可起作用,而非像inline一樣,要放在函數(shù)體定義處才起作用.
  • 他的作用是,令構(gòu)造函數(shù)的隱式調(diào)用失效.只能顯式地調(diào)用.比如復(fù)制構(gòu)造函數(shù)假如加了之后,會(huì)令等號(hào)初始化和參數(shù)值傳遞都失效.
  • 至于什么是隱式調(diào)用呢?這里先下個(gè)結(jié)論,后面詳細(xì)討論.
    • 同類型的復(fù)制涉及初始化隱式調(diào)用復(fù)制構(gòu)造:obj_type obj2; obj_type obj1 = obj2;(隱式復(fù)制構(gòu)造)與此相對應(yīng)的是顯式調(diào)用復(fù)制構(gòu)造:obj_type obj2; obj_type obj1(obj2);(顯式復(fù)制構(gòu)造)
    • 不同類型的復(fù)制初始化涉及類型轉(zhuǎn)換構(gòu)造和復(fù)制構(gòu)造(兩者的調(diào)用都是隱式)obj_type obj = 1;先調(diào)用obj_type(int)生成一個(gè)臨時(shí)對象,再通過復(fù)制構(gòu)造給obj.
    • 參數(shù)傳遞/返回值,可能涉及類型轉(zhuǎn)換構(gòu)造和復(fù)制構(gòu)造(隱式),與復(fù)制初始化類似.

我個(gè)人感覺這樣的機(jī)制挺令人惡心的。

#include <cstdio>
#define ____(str) printf("[DBG]:%s\n",str)

class base{
/*  
    private:
        base(const base& x) : val(x.val){//------------<Y>
            
        } 
*/
    public:
        base():val(0){
            ____("base()");
        }
        explicit base(int x) : val(x){//<---------------<X>
            ____("base(int x)");
        }
/*  <--------------------------------------<Z>
        base(char c):val(c - 'a'){
            ____("base(char c)");
        }
*/
        int val;
};

void foo(base x)
{
    
}

int main(){
    
    base c(10);//<--------------<0> OK
    base c1('c'); //<-----------<1> OK convert 'c' to int and call `base(int x)`
    //foo('c');//<--------------<2> Not OK by explicit. error: could not convert ‘'c'’ from ‘char’ to ‘base’
    //foo(10);//<---------------<2.2>Not OK by explicit. error: could not convert ‘10’ from ‘int’ to ‘base’
    base c1_1(10.23); //<-------<3> OK
    //base c2 = 100; //<--------<4> Copy initialize
    foo(base(10));//<-----------<5> OK call `base(int x)` explicitly. WILL AFFECTED by <Y> [COPY initial]
    return 0;
}

如果去掉<Z>處的注釋,那么:
可以得到一個(gè)非explicit得構(gòu)造函數(shù),進(jìn)而<2>和<2.2>將重新獲得編譯通過,并且foo將調(diào)用Z處的構(gòu)造函數(shù).同時(shí),<3>由于二義性而失敗.

構(gòu)造函數(shù)的種類以及他們什么時(shí)候被調(diào)用
  • 默認(rèn)構(gòu)造函數(shù) (沒有任何參數(shù)的構(gòu)造函數(shù)稱為默認(rèn)構(gòu)造函數(shù))
  • 類型轉(zhuǎn)換構(gòu)造函數(shù)(只有一個(gè)參數(shù),且參數(shù)類型與該類的類型不一樣)
    • 顯式類型轉(zhuǎn)換構(gòu)造
    • 隱式類型轉(zhuǎn)換構(gòu)造
  • 復(fù)制構(gòu)造函數(shù)(形如 class_type(const class_type &)的)
    • 復(fù)制構(gòu)造函數(shù)會(huì)被調(diào)用的情形包括:
      1). 一個(gè)對象以值傳遞的方式傳入函數(shù)體
      2). 一個(gè)對象以值傳遞的方式從函數(shù)返回
      3). 一個(gè)對象需要通過另外一個(gè)對象進(jìn)行初始化,這里包括2種情形:
      • 顯式復(fù)制構(gòu)造
      • 隱式復(fù)制構(gòu)造
      • 例如:
obj_class_type obj1;
obj_class_type obj2;
obj_class_type obj3 = obj1;//此為隱式復(fù)制構(gòu)造,假如該類的復(fù)制構(gòu)造函數(shù)聲明中帶`explicit`關(guān)鍵字,這里會(huì)編譯錯(cuò)誤.
/*
 *另,為了做對比,這里假如有 obj_class_type obj3 = 100; 100是int型,這里涉及了:
1. 一個(gè)隱式類型轉(zhuǎn)換構(gòu)造int ----> obj_class_type;
2.一個(gè)隱式復(fù)制構(gòu)造調(diào)用
兩個(gè)構(gòu)造函數(shù)必須都沒有explicit關(guān)鍵字才行!
當(dāng)類型轉(zhuǎn)換構(gòu)造函數(shù)以explicit修飾時(shí),編譯錯(cuò)誤為:error: conversion from ‘int’ to non-scalar type ‘base’ requested
當(dāng)復(fù)制構(gòu)造函數(shù)以explicit修飾時(shí),編譯錯(cuò)誤為:error: no matching function for call to ‘base::base(base)’ 這里base是我定義的類.
但奇怪的是,假如在兩個(gè)構(gòu)造函數(shù)里分別打印信息或是修改某全局變量,卻顯示只有類型轉(zhuǎn)換構(gòu)造函數(shù)被調(diào)用。查了點(diǎn)資料,有可能是所謂的你看不見但存在的臨時(shí)對象的鍋!
  */
obj_class_type obj4(obj2);//此為顯式復(fù)制構(gòu)造

另外,初始化中的等號(hào)調(diào)用的不是賦值運(yùn)算符而是復(fù)制構(gòu)造函數(shù)!!!

為啥這里沒有operator=呢?因?yàn)檫@玩意兒根本就不是構(gòu)造函數(shù)!

附:關(guān)于隱式調(diào)用中復(fù)制構(gòu)造函數(shù)參數(shù)的const問題.

  • [Update9.6] - 由于隱式調(diào)用里是從臨時(shí)變量中復(fù)制構(gòu)造過來的,因此需要復(fù)制構(gòu)造函數(shù)中參數(shù)必須為const.
附:一段丑陋且讓人摸不著頭腦的代碼

-------------8.1更新,這就是傳說中的復(fù)制初始化問題Copy Initialization--------------
可以查看這個(gè)thread和這個(gè)thread.
大家千萬不要被代碼運(yùn)行結(jié)果和網(wǎng)上眾多復(fù)制來復(fù)制去的中文資料所迷惑,直接stackoverflow或上c++ref去查.
主要觀點(diǎn)就是:
base b = 100;
base b(100);
這兩句,從結(jié)果上來看,都是調(diào)用參數(shù)為int類型的類型轉(zhuǎn)換構(gòu)造函數(shù)(conversion constructor),但對于第一句編譯器內(nèi)部是會(huì)產(chǎn)生一個(gè)臨時(shí)對象(調(diào)用類型轉(zhuǎn)換構(gòu)造函數(shù))然后 通過復(fù)制構(gòu)造函數(shù)來把這個(gè)臨時(shí)對象復(fù)制給對象b,那你會(huì)問,為啥結(jié)果沒有顯示呢?下面這個(gè)截圖可以告訴你:我們的復(fù)制構(gòu)造函數(shù)被編譯器優(yōu)化了!他的做法是把復(fù)制構(gòu)造函數(shù)放進(jìn)private進(jìn)行驗(yàn)證,而我們這是用explicit進(jìn)行驗(yàn)證.

來自[thread](http://blog.csdn.net/ljianhui/article/details/9245661)的截圖
#include <cstdio>
#include <string>
#define ____(name,str) printf("[OBJ:%s] %s\n", name.c_str(),str)

static std::string OBJ_NAME = "ABCD";

class base{
    public:
        base() : val(){ n = OBJ_NAME; OBJ_NAME = OBJ_NAME + "#"; }
        
        base(int x) : val(x){ //類型轉(zhuǎn)換構(gòu)造函數(shù)
            n = OBJ_NAME; 
            OBJ_NAME = OBJ_NAME + "#";
            ____(n,"base(int x) is called!");
        }
        
        base(const base& x);//explicit base(const base& x); ******<1> Complile_Error:error: no matching function for call to ‘base::base(base)’
        
        base& operator=(const base& x){
            if(this == &x){
                //
            }   
            ____(n,"operator= is called!");
            return *this;
        }
        
        
        ~base(){
            ____(n,"destructor is called!");
        }
        
        void name(){
            ____(n,"");
        }
        
        int val;
        std::string n;
};
base::base(const base& x) : val(x.val){ 
    n = OBJ_NAME; 
    OBJ_NAME = OBJ_NAME + "#";
    ____(n,"copy construct is called!");
}
void foo2(const base& x)//void foo2(base& x)*******<2>
{
    
}

void foo(base x)
{
    
}
int main(){
    base b = 100;//********<x>
    foo(200);//*********<y>
    foo2(201);
    
    return 0;
}

上面這段代碼運(yùn)行結(jié)果如下

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
[OBJ:ABCD] base(int x) is called!
[OBJ:ABCD#] base(int x) is called!
[OBJ:ABCD#] destructor is called!
[OBJ:ABCD##] base(int x) is called!
[OBJ:ABCD##] destructor is called!
[OBJ:ABCD] destructor is called!

<x>所在的這行代碼發(fā)生了什么?
我推測應(yīng)該是:

  1. 100這個(gè)int生成了一個(gè)臨時(shí)對象(調(diào)用了類型轉(zhuǎn)換構(gòu)造函數(shù))
  2. b對象的復(fù)制構(gòu)造構(gòu)造函數(shù)通過傳入這個(gè)臨時(shí)對象(該函數(shù)的參數(shù)必須是const類型,否則編譯出錯(cuò))
    但是,結(jié)果卻顯示沒有任何復(fù)制構(gòu)造函數(shù)被調(diào)用.
    好,那既然沒有任何復(fù)制構(gòu)造函數(shù)被調(diào)用,那我就把<1>所在行聲明為explicit,因?yàn)槲抑粚?fù)制構(gòu)造函數(shù)做的改變,按理說應(yīng)該沒有影響才對,但奇特的是卻編譯錯(cuò)誤.no matching function for call to ‘base::base(base)’

另外還有一個(gè)問題就是<y>所在行代碼發(fā)生了什么?
我推測應(yīng)該也是:

  1. 200這個(gè)int生成了一個(gè)臨時(shí)對象(調(diào)用了類型轉(zhuǎn)換構(gòu)造函數(shù))
  2. 值傳遞,該臨時(shí)對象通過復(fù)制構(gòu)造函數(shù),復(fù)制給了參數(shù)x.
    然而,也沒有任何證據(jù)顯示復(fù)制構(gòu)造函數(shù)被調(diào)用,好嘛,那我同樣把復(fù)制構(gòu)造函數(shù)設(shè)為explicit,你們這些隱式的調(diào)用都給老子去屎,果然,編譯錯(cuò)誤,foo(200)這行no matching function for call to ‘base::base(base)’.

所以,結(jié)論:雖然復(fù)制構(gòu)造函數(shù)在運(yùn)行的時(shí)候沒有體現(xiàn)被調(diào)用,但依然會(huì)幽靈一樣對實(shí)驗(yàn)程序的運(yùn)行帶來影響.所以這里就有人建議不要用帶等號(hào)的初始化。

<2>所在處的參數(shù)必須為const,否則編譯錯(cuò)誤.原因是c++對于臨時(shí)對象只能以const引用,否則讓你修改了臨時(shí)對象,那還得了?!同理,復(fù)制構(gòu)造函數(shù)的參數(shù)也只能是const,否則,你執(zhí)行帶等號(hào)的初始化(也就是復(fù)制初始化)時(shí),一樣會(huì)出現(xiàn)這個(gè)錯(cuò)誤!

  • [Update9.6] - 上面這段加上編譯參數(shù)-fno-elide-constructors后,結(jié)果如下:(露出了真面目)
work@vm1:initialize$ g++ piece.cc -o test -fno-elide-constructors
work@vm1:initialize$ ./test
[OBJ:ABCD] base(int x) is called!
[OBJ:ABCD#] copy construct is called!
[OBJ:ABCD] destructor is called!
[OBJ:ABCD##] base(int x) is called!
[OBJ:ABCD###] copy construct is called!
[OBJ:ABCD###] destructor is called!
[OBJ:ABCD##] destructor is called!
[OBJ:ABCD####] base(int x) is called!
[OBJ:ABCD####] destructor is called!
[OBJ:ABCD#] destructor is called!
附: 關(guān)于編譯器自動(dòng)生成的構(gòu)造函數(shù).

  1. 假如用戶定義類型沒有任何構(gòu)造函數(shù),編譯器會(huì)自動(dòng)生成的函數(shù):(只要有需要的話)
  • 默認(rèn)構(gòu)造函數(shù) (沒有任何參數(shù)的構(gòu)造函數(shù)稱為默認(rèn)構(gòu)造函數(shù))
  • 復(fù)制構(gòu)造函數(shù)
  • 析構(gòu)函數(shù) ----之后討論析構(gòu)
  • 賦值運(yùn)算符
  1. 假如用戶定義了某個(gè)構(gòu)造函數(shù),編譯器就不會(huì)自動(dòng)生成默認(rèn)構(gòu)造函數(shù)(*),但如果有需要,還是會(huì)自動(dòng)生成其他3個(gè)函數(shù).
    若用戶沒有定義默認(rèn)構(gòu)造函數(shù),那么C風(fēng)格的聲明該類會(huì)報(bào)錯(cuò).
    base2 b2_1;base2 b2_1_1 = base2();例如這些,其中base2類你定義了一個(gè)構(gòu)造函數(shù),但沒默認(rèn)構(gòu)造函數(shù).----error: no matching function for call to ‘base2::base2()’

  2. 編譯器自動(dòng)生成的:(都是public)

  • 默認(rèn)構(gòu)造函數(shù),就是一個(gè)空函數(shù),即什么也不做,不初始化也不在函數(shù)體內(nèi)賦值,因此只要該實(shí)例為局部變量且使用了編譯器自動(dòng)生成的默認(rèn)構(gòu)造函數(shù),則內(nèi)部成員是隨機(jī)值.
  • 復(fù)制構(gòu)造函數(shù), 按值復(fù)制,即將參數(shù)(const type& )中的各個(gè)成員賦值給本對象的各個(gè)成員。<--------一般情況下不會(huì)有啥問題,但如果你的成員中帶有一個(gè)指針成員,ok,完了,當(dāng)復(fù)制的成員析構(gòu)時(shí),可能會(huì)把原始成員的這個(gè)指針給free了。(即淺拷貝存在的問題)
  • 賦值運(yùn)算符
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,510評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評(píng)論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,331評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,536評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,754評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,273評(píng)論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,505評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容