天天看點

【轉】認識 C++ 中的 explicit 關鍵字

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;的情況(因為這樣隻能賦給一個值)。