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ù)制初始化類似.
- 同類型的復(fù)制涉及初始化隱式調(diào)用復(fù)制構(gòu)造:
我個(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)造
- 例如:
- 復(fù)制構(gòu)造函數(shù)會(huì)被調(diào)用的情形包括:
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)證.
#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)該是:
- 100這個(gè)int生成了一個(gè)臨時(shí)對象(調(diào)用了類型轉(zhuǎn)換構(gòu)造函數(shù))
- 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)該也是:
- 200這個(gè)int生成了一個(gè)臨時(shí)對象(調(diào)用了類型轉(zhuǎn)換構(gòu)造函數(shù))
- 值傳遞,該臨時(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ù).
- 假如用戶定義類型沒有任何構(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)算符
假如用戶定義了某個(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()’
編譯器自動(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)算符