C++ 中 explicit 關鍵字的作用
在C++中,explicit關鍵字用來修飾類的構造函數,被修飾的構造函數的類,不能發生相應的隐式類型轉換,隻能以顯示的方式進行類型轉換。
explicit使用注意事項:explicit 關鍵字隻能用于類内部的構造函數聲明上,explicit 關鍵字作用于單個參數的構造函數。
在C++中,explicit關鍵字用來修飾類的構造函數,被修飾的構造函數的類,不能發生相應的隐式類型轉換。
在C++中,如果一個類有隻有一個參數的構造函數,C++允許一種特殊的聲明類變量的方式。在這種情況下,可以直接将一個對應于構造函數參數類型的資料直接指派給類變量,編譯器在編譯時會自動進行類型轉換,将對應于構造函數參數類型的資料轉換為類的對象。如果在構造函數前加上explicit修飾詞,則會禁止這種自動轉換,在這種情況下,即使将對應于構造函數參數類型的資料直接指派給類變量,編譯器也會報錯。
下面以具體執行個體來說明。
建立people.cpp 檔案,然後輸入下列内容:
class People
{
public:
int age;
People (int a)
{
age=a;
}
};
void foo ( void )
{
People p1(10); //方式一
People* p_p2=new People(10); //方式二
People p3=10; //方式三
}
這段C++程式定義了一個類people,包含一個構造函數,這個構造函數隻包含一個整形參數a,可用于在構造類時初始化age變量。
然後定義了一個函數foo,在這個函數中我們用三種方式分别建立了三個10歲的“人”。第一種是最一般的類變量聲明方式。第二種方式其實是聲明了一個people類的指針變量,然後在堆中動态建立了一個people執行個體,并把這個執行個體的位址指派給了p_p2.第三種方式就是我們所說的特殊方式,為什麼說特殊呢?我們都知道,C/C++是一種強類型語言,不同的資料類型是不能随意轉換的,如果要進行類型轉換,必須進行顯式強制類型轉換,而這裡,沒有進行任何顯式的轉換,直接将一個整型資料指派給了類變量p3.
是以,可以說,這裡進行了一次隐式類型轉換,編譯器自動将對應于構造函數參數類型的資料轉換為了該類的對象,是以方式三經編譯器自動轉換後和方式一最終的實作方式是一樣的。
不相信?耳聽為虛,眼見為實,讓我們看看底層的實作方式。
為了更容易比較方式一和方式三的實作方式,我們對上面的代碼作一點修改,去除方式二:
void foo ( void )
{
People p1(10); //方式一
People p3=10; //方式三
}
去除方式二的原因是方式二是在堆上動态建立類執行個體,是以會有一些額外代碼影響分析。修改完成後,用下列指令編譯people.cpp
$ gcc -S people.cpp
"-S"選項是GCC輸出彙編代 碼。指令執行後,預設生成people.s。關鍵部分内容如下:
.globl _Z3foov
.type _Z3foov, @function
_Z3foov:
.LFB5:
pushl %ebp
.LCFI2:
movl %esp, %ebp
.LCFI3:
subl $24, %esp
.LCFI4:
movl $10, 4(%esp)
leal -4(%ebp), %eax
movl %eax, (%esp)
call _ZN6PeopleC1Ei
movl $10, 4(%esp)
leal -8(%ebp), %eax
movl %eax, (%esp)
call _ZN6PeopleC1Ei
leave
ret
看“.LCFI4”行後面的東西,1-4行和5-8行幾乎一模一樣,1-4行即為方式一的彙編代碼,5-8即為方式三的彙編代碼。細心的你可能發現2和6行有所不同,一個是-4(%ebp)而另一個一個是-8(%ebp),這分别為類變量P1和P3的位址。
對于不可随意進行類型轉換的強類型語言C/C++來說,這可以說是C++的一個特性。哦,今天好像不是要說C++的特性,而是要知道explicit關鍵字的作用?
explicit關鍵字到底是什麼作用呢?它的作用就是禁止這個特性。如文章一開始而言,凡是用explicit關鍵字修飾的構造函數,編譯時就不會進行自動轉換,而會報錯。
讓我們看看吧!修改代碼:
classPeople
{
public:
int age;
explicit People (int a)
{
age=a;
}
};
然後再編譯:
$ gcc -S people.cpp
編譯器立馬報錯:
people.cpp: In function ‘void foo()’:
people.cpp:23: 錯誤:請求從 ‘int’轉換到非标量類型‘People’
以下再以幾個例子來加深印象:
例子一:
未加explicit時的隐式類型轉換
1. class Circle
2. {
3. public:
4. Circle(double r) : R(r) {}
5. Circle(int x, int y = 0) : X(x), Y(y) {}
6. Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}
7. private:
8. double R;
9. int X;
10. int Y;
11. };
12.
13. int _tmain(int argc, _TCHAR* argv[])
14. {
15. //發生隐式類型轉換
16. //編譯器會将它變成如下代碼
17. //tmp = Circle(1.23)
18. //Circle A(tmp);
19. //tmp.~Circle();
20. Circle A = 1.23;
21. //注意是int型的,調用的是Circle(int x, int y = 0)
22. //它雖然有2個參數,但後一個有預設值,仍然能發生隐式轉換
23. Circle B = 123;
24. //這個算隐式調用了拷貝構造函數
25. Circle C = A;
26. return 0;
27. }
加了explicit關鍵字後,可防止以上隐式類型轉換發生
1. class Circle
2. {
3. public:
4. explicit Circle(double r) : R(r) {}
5. explicit Circle(int x, int y = 0) : X(x), Y(y) {}
6. explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}
7. private:
8. double R;
9. int X;
10. int Y;
11. };
12.
13. int _tmain(int argc, _TCHAR* argv[])
14. {
15. //一下3句,都會報錯
16. //Circle A = 1.23;
17. //Circle B = 123;
18. //Circle C = A;
19.
20. //隻能用顯示的方式調用了
21. //未給拷貝構造函數加explicit之前可以這樣
22. Circle A = Circle(1.23);
23. Circle B = Circle(123);
24. Circle C = A;
25.
26. //給拷貝構造函數加了explicit後隻能這樣了
27. Circle A(1.23);
28. Circle B(123);
29. Circle C(A);
30. return 0;
31. }
例子二:
class A
{
public:
A(int);
private:
int num;
};
int Test(const A&) // 一個應用函數
{
...
}
Test(2); // 正确
過程是這樣的: 編譯器知道傳的值是int而函數需要的是A類型,但它也同時知道調用A的構造函數将int轉換成一個合适的A,是以才有上面成功的調用.換句話說,編譯器處理這個調用時的情形類似下面這樣:
const A temp(2); // 從2産生一個臨時A對象
Test(temp); // 調用函數
如果代碼寫成如下樣子:
class A
{
public:
explicit A(int);
private:
int num;
};
int Test(const A&) // 一個應用函數
{
...
}
Test(2); // 失敗,不能通過隐式類型轉換将int類型變量構造成成A類型變量
例子三:
按照預設規定,隻有一個參數的構造函數也定義了一個隐式轉換,将該構造函數對應資料類型的資料轉換為該類對象,如下面所示:
class String {
String ( const char* p ); // 用C風格的字元串p作為初始化值
//…
}
String s1 = “hello”; //OK 隐式轉換,等價于String s1 = String(“hello”);
但是有的時候可能會不需要這種隐式轉換,如下:
class String {
String ( int n ); //本意是預先配置設定n個位元組給字元串
String ( const char* p ); // 用C風格的字元串p作為初始化值
//…
}
下面兩種寫法比較正常:
String s2 ( 10 ); //OK 配置設定10個位元組的空字元串
String s3 = String ( 10 ); //OK 配置設定10個位元組的空字元串
下面兩種寫法就比較疑惑了:
String s4 = 10; //編譯通過,也是配置設定10個位元組的空字元串
String s5 = ‘a’; //編譯通過,配置設定int(‘a’)個位元組的空字元串
s4 和s5 分别把一個int型和char型,隐式轉換成了配置設定若幹位元組的空字元串,容易令人誤解。為了避免這種錯誤的發生,我們可以聲明顯示的轉換,使用explicit 關鍵字:
class String {
explicit String ( int n ); //本意是預先配置設定n個位元組給字元串
String ( const char* p ); // 用C風格的字元串p作為初始化值
//…
}
加上explicit,就抑制了String ( int n )的隐式轉換,下面兩種寫法仍然正确:
String s2 ( 10 ); //OK 配置設定10個位元組的空字元串
String s3 = String ( 10 ); //OK 配置設定10個位元組的空字元串
下面兩種寫法就不允許了:
String s4 = 10; //編譯不通過,不允許隐式的轉換
String s5 = ‘a’; //編譯不通過,不允許隐式的轉換
是以,某些時候,explicit 可以有效得防止構造函數的隐式轉換帶來的錯誤或者誤解
----------------------------------------------------------
explicit隻對構造函數起作用,用來抑制隐式轉換。如:
class A{
A(int a);
};
int Function(A a);
當調用Function(2)的時候,2會隐式轉換為A類型。這種情況常常不是程式員想要的結果,是以,要避免之,就可以這樣寫:
class A{
explicit A(int a);
};
int Function(A a);
這樣,當調用Function(2)的時候,編譯器會給出錯誤資訊(除非Function有個以int為參數的重載形式),這就避免了在程式員毫不知情的情況下出現錯誤。
注意:隻是用于一個參數的構造函數( 如:1、constructor(typename value); 2、construcor(typename value1,typename value2=defaultvalue,typename value3=defaultvalue,...) ),因為兩個參數的構造函數幾乎沒辦法隐式的轉換,即無法出現classtype classname = value;的情況(因為這樣隻能賦給一個值)。