C++ explicit 用法與範例

本篇 ShengYu 介紹 C++ explicit 用法與範例,C++ 裡有隱性轉換 (implicit conversion) 跟顯性轉換 (explicit conversion),今天來介紹什麼是隱性轉換?什麼是顯性轉換?並且示範一下這兩者的差異。

C++ explicit 基本用法與範例

在 C++ 中 explicit 這個關鍵字最常出現在建構子前面,我們就來介紹這者差異是什麼,如下列範例所示,MyInteger n1 = 5; 寫法是數值寫在等號右邊,編譯器會自動幫你去轉換成 MyInteger(int n),這個轉換就是隱性轉換,而 MyInteger n2(6); 寫法是很明確地直接呼叫建構子,這個轉換就是顯性轉換,這就是這兩種的差異。

以下這個範例示範支援隱性轉換和顯性轉換的寫法,

cpp-explicit.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// g++ cpp-explicit.cpp -o a.out
#include <iostream>
using namespace std;

class MyInteger {
public:
MyInteger(int n) : data(n) {
cout << "data = " << data << "\n";
}
private:
int data;
};

int main() {
MyInteger n1 = 5;
MyInteger n2(6);
return 0;
}

輸出如下,

1
2
data = 5
data = 6

那今天我們想要禁止隱性轉換這種寫法的話只需要在 MyInteger(int n) 建構子前面加上 explicit 關鍵字,告訴編譯器只能用顯性轉換,那這樣的隱性轉換寫法編譯器就會在編譯時期檢查時就會擋下來,出現編譯錯誤給開發者知道,這樣我們就能夠成功地防止開發者去寫這種程式碼,

1
2
3
4
cpp-explicit.cpp: In function ‘int main()’:
cpp-explicit.cpp:15:20: error: conversion from ‘int’ to non-scalar type ‘MyInteger’ requested
MyInteger n1 = 5;
^

接著開發者只能寫 MyInteger n2(6); 呼叫建構子 direct-initialization 的寫法了,而原本的 MyInteger n1 = 5; copy-initialization 寫法就不行使用了,如下所示,

cpp-explicit2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// g++ cpp-explicit2.cpp -o a.out
#include <iostream>
using namespace std;

class MyInteger {
public:
explicit MyInteger(int n) : data(n) {
cout << "data = " << data << "\n";
}
private:
int data;
};

int main() {
//MyInteger n1 = 5;
MyInteger n2(6);
return 0;
}

輸出如下,

1
data = 6

C++ explicit 實際範例

剛剛上述已經了解了隱性轉換 implicit conversion 跟顯性轉換 explicit conversion 的差異,但什麼時候會需要用到 explicit 呢?這種情形大概常發生在以下多個建構子情形中,
如下列範例所示,有一個 MyString 類別裡有兩種建構子,一種是傳入整數 n 以便動態記憶體配置 n 的大小的 char,另一種是傳入指向字元陣列的指標然後在裡面複製一份,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ cpp-explicit3.cpp -o a.out
#include <iostream>
using namespace std;

class MyString {
public:
MyString(int n) { // allocate n bytes to the MyString object
cout << "MyString(int n)\n";
}

MyString(const char *p) { // initializes object with char *p
cout << "MyString(const char *p)\n";
}
};

int main() {
MyString s1 = 10;
MyString s2(10);
MyString s3 = "hello world";
MyString s4("123456");
return 0;
}

輸出如下,那我們目前可以接受這四種初始化寫法,以及實際上這四種寫法對應到的哪個建構子,

1
2
3
4
MyString(int n)
MyString(int n)
MyString(const char *p)
MyString(const char *p)

可是 s1 的寫法讓人容易誤會,跟 s4 很像,為了不讓其他開發者混淆,所以我想避免這種隱式轉換,那麼我就在 MyString(int n) 前加上 explicit,結果就變成下面這樣的例子,為了更好理解這個範例,我特地把程式碼寫的更詳細完整一點,讓我們看看結果會怎樣吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// g++ cpp-explicit4.cpp -o a.out
#include <iostream>
#include <string.h>
using namespace std;

class MyString {
public:
explicit MyString(int n) { // allocate n bytes to the MyString object
data = new char[n];
cout << "MyString(int n)\n";
}

MyString(const char *p) { // initializes object with char *p
int len = strlen(p);
data = new char[len+1];
strcpy(data, p);
cout << "MyString(const char *p), data = " << data << "\n";
}

~MyString() {
delete[] data;
}
private:
char *data;
};

int main() {
//MyString s1 = 10;
MyString s2(10);
MyString s3 = "hello world";
MyString s4("123456");
return 0;
}

結果輸出如下,如果再使用 s1 這種 copy-initialization 寫法就會出現編譯錯誤,開發者就不會使用這種寫法了,改完之後剩下這幾種寫法就比較原本的明確多了,

1
2
3
MyString(int n)
MyString(const char *p), data = hello world
MyString(const char *p), data = 123456

當然實際上可能不只這種情形,但我們了解了 explicit 用法後,我們就能依照當時的狀況選擇要不要使用 explicit 這個關鍵字,以上就是 ShengYu 對 C++ explicit 的簡單用法介紹,希望讓你對 explicit 有更深刻的體會。

其它參考
explicit specifier - cppreference.com
https://en.cppreference.com/w/cpp/language/explicit
c++ - What does the explicit keyword mean? - Stack Overflow
https://stackoverflow.com/questions/121162/what-does-the-explicit-keyword-mean

其它相關文章推薦
C/C++ 新手入門教學懶人包
C/C++ 字串轉數字的4種方法
C++ virtual 的兩種用法
C/C++ 字串反轉 reverse
C/C++ call by value傳值, call by pointer傳址, call by reference傳參考 的差別
C++ 類別樣板 class template
std::sort 用法與範例
std::find 用法與範例
std::queue 用法與範例
std::map 用法與範例
std::deque 用法與範例
std::vector 用法與範例