網站首頁 編程語言 正文
前言
最近在閱讀android底層源碼的時候,發現其中好多代碼使用了explicit關鍵字,因此這里對explicit關鍵字進行了分析和介紹。
1. 抑制構造函數定義的隱式轉換
在要求隱式轉換的程序上下文中,我們可以通過將構造函數聲明為explicit加以組織:
class Sales_data {
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) {}
explicit Sales_data(const std::string &s): bookNo(s) {}
explicit Sales_data(std::istream&);
Sales_data& combine(const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;;
return *this;
}
private:
double avg_price() const {return units_sold ? revenue / units_sold : 0; }
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
此時,沒有任何構造函數能用于隱式地創建Sales_data對象:下面的兩種用法都無法通過編譯:
Sales_data item; // right, 調用默認構造函數
Sales_data item2("book"); // right, 調用explicit Sales_data(const std::string &s): bookNo(s) {}
item.combine(null_book); // error: string構造函數式explicit的
item.combine(cin); // error: istream構造函數式explicit的
關鍵字 explicit 只對一個實參的構造函數有效。需要多個實參的構造函數不能用于執行隱式轉換,所以無須將這些構造函數指定為 explicit 的。只能在類內聲明構造函數時使用 explicit 關鍵字,在類外部定義時不應重復:
// error: explicit 關鍵字只允許出現在類內的構造函數聲明處
explicit Sales_data::Sales_data(istream& is) {
read(is, *this);
}
- note1: explicit 構造函數只能用于直接初始化。
- note2: 當使用explicit 關鍵字聲明構造函數時,它將只能以直接初始化的形式使用。而且,編譯器將不會在自動轉換過程中使用該構造函數。
發生隱式轉換的一種情況時當我們執行拷貝的初始化時(使用 = )。此時,我們只能使用直接初始化而不能使用explicit構造函數:
Sales_data null_book("book", 1, 10.0); // right
Sales_data item1(null_book); // right,直接初始化
Sales_data item2 = null_book; // error, 不能將explicit 構造函數用于拷貝形式的初始化過程
2. 為轉換顯式地使用構造函數
盡管編譯器不會將 explicit 的構造函數用于隱式轉換過程,但是我們可以使用這樣的構造函數顯式地強制進行轉換:
Sales_data null_book("book", 1, 10.0); // right
// right: 直接初始化
item.combine(Sales_data(null_book));
// right: static_cast可以使用explicit的構造函數
item.combine(static_cast<Sales_data>(cin));
在第一個調用中,我們直接使用Sales_data的構造函數,該調用通過接受string構造函數創建了一個臨時的 Sales_data 對象。在第二個調用中,我們使用 static_cast 執行了顯式的而非隱式的轉換。其中 static_cast 使用 istram 的構造函數創建了一個臨時的Sales_data對象。
3. 類型轉換運算符可能產生意外結果
《C++ prime》第五版,14.9.1中關于類型轉換的介紹:
在實踐中,類很少提供類型轉換運算符。在大多數情況下,如果類型轉換自動發生,用戶可能會感覺比較意外,而不是感覺受到了幫助。然而這條經驗法則存在一種例外情況:對于類來說,定義向bool的類型轉換還是比較普遍的現象。
在C++標準的早期版本中,如果類想定義一個向bool的類型轉換,則它常常遇到一個問題:因為bool是一種算術類型,所以類類型的對象轉換成bool后就能被用在任何需要算數類型的上下文中。這樣的類型轉換可能引發意想不到的結果,特別是當istream含有向bool的類型轉換時,下面的代碼仍將通過編譯:
int i = 42;
cin << i; // 如果向bool的類型轉換不是顯式的,則該代碼在編譯器看來將是合法的!
// 這個程序只有在輸入數字的時候,i會默認為整數,輸入字符串則會為0
這段程序視圖將輸出運算符用作輸入流。因為istream本身并沒有定義<<,所以本來代碼應該產生錯誤。然而,該代碼能使用istream的bool類型轉換運算符將cin轉換成bool,而這個bool值接著會被提升成int并用作內置的左移運算符的左側運算對象。這樣一來,提升后的bool值(1或0)最終會被左移42個位置。這一結果顯示與我們的預期大相徑庭。
4. 顯示的類型轉換運算符
為了防止這樣的異常情況發生,C++11新標準引入了顯式的類型轉換運算符(explicit conversion operator):
class SmallInt {
public:
// 編譯器不會自動執行這一類型轉換
explicit operator int() const {return val;}
// 其他成員與之前的版本一致
};
和顯示的構造函數一樣,編譯器(通常)也不會將一個顯式的類型轉換運算符用于隱式類型轉換:
SmallInt si = 3; // 正確:SmallInt的構造函數不是顯式的
si + 3; // 錯誤:此處需要隱式的類型轉換,但類的運算符是顯式的
static_cast<int>(si) + 3; // 正確:顯示地請求類型轉換。這里的static_cast<int>可以進行強制類型轉換
當類型轉換運算符是顯式的時,我們也能執行類型轉換,不過必須通過顯式的強制類型轉換才可以。
該規定存在一個例外,即如果表達式被用作條件,則編譯器會將顯式的類型轉換自動應用于它。換句話說,當表達式出現在下列位置時,顯式的類型轉換將被隱式地執行:
- if、while及do語句的條件部分
- for 語句頭的條件表達式
- 邏輯非(!)、邏輯或(||)、邏輯與(&&)的運算對象
- 條件運算符(? : )的條件表達式
5. explicit練習
5.1 當不使用explict關鍵字時
// explicit關鍵字的作用就是防止類構造函數的隱式自動轉換
// 并且explicit關鍵字只對有一個參數的類構造函數有效,如果類構造函數參數大于
// 或等于兩個時,是不會產生隱式轉換的,所有explicit關鍵字也就無效了。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
class CxString // 這里沒有使用explicit關鍵字的類聲明,即默認為隱式聲明
{
public:
char *_pstr;
int _size;
CxString(int size) {
cout << "CxString(int size), size = " << size << endl;
_size = size; // string的預設大小
_pstr = (char*)malloc(size + 1);
memset(_pstr, 0, size + 1);
}
CxString(const char *p) {
int size = strlen(p);
_pstr = (char*)malloc(size + 1); // 分配string的內存
strcpy(_pstr, p);
_size = strlen(_pstr);
cout << "CxString(const char *p), strlen(p) = " << size << endl;
}
~CxString() {
if (_pstr != nullptr) {
delete(_pstr);
_pstr = nullptr;
}
}
};
int main() {
CxString string1(24); // right, 為CxString預分配24字節的大小的內存
CxString string2 = 10; // right, 為CxString預分配10字節的大小的內存
CxString string3; // error, 因為沒有默認構造函數, 錯誤為: “CxString”: 沒有合適的默認構造函數可用
CxString string4("aaaa"); // right
CxString string5 = "bbb"; // right, 調用的是CxString(const char *p)
CxString string6 = 'c'; // right, 其實調用的是CxString(int size), 且size等于'c'的ascii碼
string1 = 2; // right, 為CxString預分配2字節的大小的內存
string2 = 3; // right, 為CxString預分配3字節的大小的內存
CxString string3 = string1; // right, 至少編譯是沒問題的, 但是如果析構函數里用free釋放_pstr內存指針的時候可能會報錯, 完整的代碼必須重載運算符"=", 并在其中處理內存釋放
return 0;
}
上面的代碼中, “CxString string2 = 10;” 這句為什么是可以的呢? 在C++中, 如果的構造函數只有一個參數時, 那么在編譯的時候就會有一個缺省的轉換操作:將該構造函數對應數據類型的數據轉換為該類對象. 也就是說 “CxString string2 = 10;” 這段代碼, 編譯器自動將整型轉換為CxString類對象, 實際上等同于下面的操作:
CxString string2(10);
或
CxString temp(10);
CxString string2 = temp;
但是, 上面的代碼中的_size代表的是字符串內存分配的大小, 那么調用的第二句 “CxString string2 = 10;” 和第六句 “CxString string6 = ‘c’;” 就顯得不倫不類, 而且容易讓人疑惑. 有什么辦法阻止這種用法呢? 答案就是使用explicit關鍵字. 我們把上面的代碼修改一下, 如5.2小節。
5.2 使用explict關鍵字時
// explicit關鍵字的作用就是防止類構造函數的隱式自動轉換
// 并且explicit關鍵字只對有一個參數的類構造函數有效,如果類構造函數參數大于
// 或等于兩個時,是不會產生隱式轉換的,所有explicit關鍵字也就無效了。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
class CxString // 這里沒有使用explicit關鍵字的類聲明,即默認為隱式聲明
{
public:
char *_pstr;
int _size;
int _age;
explicit CxString(int size) {
cout << "CxString(int size), size = " << size << endl;
_size = size; // string的預設大小
_pstr = (char*)malloc(size + 1);
memset(_pstr, 0, size + 1);
}
CxString(const char *p) {
int size = strlen(p);
_pstr = (char*)malloc(size + 1); // 分配string的內存
strcpy(_pstr, p);
_size = strlen(_pstr);
cout << "CxString(const char *p), strlen(p) = " << size << endl;
}
// 上面也已經說過了, explicit關鍵字只對有一個參數的類構造函數有效。
// 如果類構造函數參數大于或等于兩個時, 是不會產生隱式轉換的, 所以explicit關鍵字也就無效了.
explicit CxString(int age, int size) {
_age = age;
_size = size;
}
~CxString() {
if (_pstr != nullptr) {
delete(_pstr);
_pstr = nullptr;
}
}
};
int main() {
CxString string1(24); // right, 為CxString預分配24字節的大小的內存
CxString string2 = 10; // error, 因為取消了隱式轉換
CxString string3; // error, 因為沒有默認構造函數, 錯誤為: “CxString”: 沒有合適的默認構造函數可用
CxString string4("aaaa"); // right
CxString string5 = "bbb"; // right, 調用的是CxString(const char *p)
CxString string6 = 'c'; // error, 其實調用的是CxString(int size), 且size等于'c'的ascii碼, 因為取消了隱式轉換
string1 = 2; // error, 因為取消了隱式轉換
string2 = 3; // error, 因為取消了隱式轉換
CxString string3 = string1; // right, 至少編譯是沒問題的, 但是如果析構函數里用free釋放_pstr內存指針的時候可能會報錯, 完整的代碼必須重載運算符"=", 并在其中處理內存釋放
return 0;
}
5.3 explicit 標識的構造函數中存在一個默認值
但是, 也有一個例外, 就是當除了第一個參數以外的其他參數都有默認值的時候, explicit關鍵字依然有效, 此時, 當調用構造函數時只傳入一個參數, 等效于只有一個參數的類構造函數,
例子如下:
class CxString // 使用關鍵字explicit聲明
{
public:
int _age;
int _size;
// 此時該構造函數等效于只有一個參數的類構造函數,explicit可以生效
explicit CxString(int age, int size = 0)
{
_age = age;
_size = size;
// 代碼同上, 省略...
}
CxString(const char *p)
{
// 代碼同上, 省略...
}
};
// 下面是調用:
CxString string1(24); // right
CxString string2 = 10; // error, 因為explicit關鍵字取消了隱式轉換
CxString string3; // error, 因為沒有默認構造函數
string1 = 2; // error, 因為取消了隱式轉換
string2 = 3; // error, 因為取消了隱式轉換
string3 = string1; // error, 因為取消了隱式轉換, 除非類實現操作符"="的重載
原文鏈接:https://blog.csdn.net/weixin_45676049/article/details/125980252
相關推薦
- 2023-04-27 react中關于函數調用()與bind?this的原因及分析_React
- 2022-09-15 Android?Jetpack庫剖析之LiveData組件篇_Android
- 2022-06-07 Python中的引用和拷貝規律詳解_python
- 2022-08-21 golang數組內存分配原理_Golang
- 2022-09-30 C#?wpf?Canvas中實現控件拖動調整大小的示例_C#教程
- 2023-01-20 React?useMemo與useCallabck有什么區別_React
- 2022-12-25 python實現超時退出的三種方式總結_python
- 2022-12-25 一文帶你了解Go語言中的指針和結構體_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支