深度剖析C++17中的std::optional:處理可能缺失值的利器

生成特定比例的圖片.png

文章目錄

    • 一、基本概念與設計理念
    • 二、構建與初始化
      • (一)默認構造
      • (二)值初始化
      • (三)使用`std::make_optional`
      • (四)使用`std::nullopt`
    • 三、訪問值
      • (一)`value()`
      • (二)`value_or(T default_value)`
      • (三)使用解引用操作符`*`和`->`
    • 四、修改器
      • (一)`reset()`
      • (二)`emplace()`
      • (三)`operator=`
    • 五、觀察器
      • (一)`has_value()`
      • (二)`operator bool()`
      • (三)`value_type`
    • 六、使用場景
      • (一)函數返回值
      • (二)容器元素
      • (三)避免空指針異常
    • 七、性能考慮
    • 八、總結

在C++17的標準庫中, std::optional是一個極為實用的工具,它為處理可能缺失的值提供了一種安全、高效且直觀的方式。在傳統的C++編程里,處理可能不存在的值是一個棘手的問題,通常依賴于特殊標記值,比如指針設為 nullptr,整數設為 -1 ,或者浮點數設為 std::numeric_limits<T>::quiet_NaN()等。但這些方式容易引入潛在的錯誤,尤其是當特殊標記值與合法數據值沖突時,還會讓代碼邏輯變得復雜難讀。 std::optional的出現,很好地解決了這些痛點。

一、基本概念與設計理念

std::optional是C++17引入的一個模板類,它的設計目的是清晰地表達一個值可能存在,也可能不存在的情況。從本質上來說,std::optional是一個包含了一個值或者什么都不包含的對象。它通過將值的存在性和值本身封裝在一起,使得代碼能夠更明確地處理可能缺失值的場景,提升了代碼的安全性和可讀性。

例如,考慮一個從數據庫中獲取用戶年齡的函數。在某些情況下,數據庫中可能沒有記錄該用戶的年齡信息。使用std::optional,可以這樣編寫代碼:

#include <optional>// 假設這是從數據庫獲取年齡的函數
std::optional<int> getAgeFromDatabase(int userId) {// 這里模擬數據庫查詢邏輯,假設某些情況下沒有年齡數據if (userId == 1) {return 30;} else {return std::nullopt;}
}int main() {auto age = getAgeFromDatabase(2);if (age.has_value()) {std::cout << "用戶年齡是: " << age.value() << std::endl;} else {std::cout << "未找到該用戶的年齡信息" << std::endl;}return 0;
}

在這個例子中,getAgeFromDatabase函數返回一個std::optional<int>,如果找到了年齡,就返回包含年齡值的std::optional;如果沒找到,就返回std::nullopt,表示沒有值。在main函數中,通過has_value方法檢查是否有值,再進行相應的處理,邏輯非常清晰。

二、構建與初始化

(一)默認構造

std::optional可以進行默認構造,此時它處于空狀態,即不包含任何值:

std::optional<int> opt1; // 空的std::optional

(二)值初始化

通過直接賦值的方式,可以將一個值初始化為std::optional

std::optional<std::string> opt2 = "Hello, optional";

(三)使用std::make_optional

std::make_optional是一個便捷的函數模板,用于創建std::optional對象。它會直接在內部構造值,避免了不必要的拷貝或移動操作,在性能上更有優勢:

auto opt3 = std::make_optional<std::vector<int>>({1, 2, 3});

(四)使用std::nullopt

std::nullopt是一個特殊的常量,專門用于表示std::optional為空的狀態。可以在初始化時顯式使用它來表明std::optional不包含值:

std::optional<double> opt4 = std::nullopt;

三、訪問值

(一)value()

value()方法用于獲取std::optional中存儲的值。但需要特別注意的是,如果std::optional為空,調用value()會拋出std::bad_optional_access異常。所以在調用value()之前,務必先使用has_value()方法檢查std::optional是否包含值:

std::optional<int> opt = 42;
if (opt.has_value()) {int val = opt.value();std::cout << "值是: " << val << std::endl;
}

(二)value_or(T default_value)

value_or方法提供了一種更安全的取值方式。當std::optional包含值時,它返回該值;當std::optional為空時,它返回傳入的默認值。這樣就避免了因調用value()方法在空狀態下拋出異常的風險:

std::optional<int> opt5;
int result = opt5.value_or(100); // result為100

(三)使用解引用操作符*->

std::optional重載了*->操作符,當std::optional包含值時,可以像使用普通指針一樣訪問值。*操作符返回值的引用,->操作符用于訪問值內部的成員:

class MyClass {
public:void print() {std::cout << "這是MyClass的實例" << std::endl;}
};std::optional<MyClass> opt6;
opt6.emplace();
if (opt6) {opt6->print(); // 調用MyClass的print方法(*opt6).print(); // 與上面的效果相同
}

四、修改器

(一)reset()

reset()方法用于將std::optional設置為空狀態,即移除其中存儲的值。之后再調用has_value()方法會返回false

std::optional<int> opt7 = 42;
opt7.reset();
if (!opt7.has_value()) {std::cout << "opt7現在為空" << std::endl;
}

(二)emplace()

emplace()方法允許在std::optional內部直接構造值,而不需要先移除舊值再進行賦值。這在構造復雜對象時非常有用,可以避免不必要的構造和析構開銷,提高效率:

std::optional<std::string> opt8;
opt8.emplace("新的值");

(三)operator=

可以使用賦值操作符=來修改std::optional的值。如果std::optional之前為空,賦值后會包含新值;如果之前有值,會先銷毀舊值,再存儲新值:

std::optional<int> opt9 = 10;
opt9 = 20;

五、觀察器

(一)has_value()

has_value()方法是最常用的觀察器之一,用于檢查std::optional是否包含值。在訪問std::optional中的值之前,通常會先調用這個方法進行檢查:

std::optional<double> opt10;
if (opt10.has_value()) {std::cout << "opt10有值" << std::endl;
} else {std::cout << "opt10為空" << std::endl;
}

(二)operator bool()

std::optional重載了bool類型轉換操作符,使得可以直接在條件語句中判斷std::optional是否包含值。這種方式簡潔明了,常用于簡化代碼邏輯:

std::optional<std::vector<int>> opt11 = {1, 2, 3};
if (opt11) {std::cout << "opt11包含一個非空的vector" << std::endl;
}

(三)value_type

value_typestd::optional的嵌套類型別名,用于獲取存儲值的類型。在一些需要使用類型信息的模板編程場景中非常有用:

std::optional<std::string> opt12;
using value_type = std::optional<std::string>::value_type;

六、使用場景

(一)函數返回值

在函數返回值可能缺失的情況下,std::optional能清晰地表達這種不確定性。比如在實現一個查找元素索引的函數時:

std::optional<size_t> findIndex(const std::vector<int>& vec, int target) {for (size_t i = 0; i < vec.size(); ++i) {if (vec[i] == target) {return i;}}return std::nullopt;
}

(二)容器元素

std::optional可以作為容器的元素,用于表示容器中可能存在缺失值的情況。例如,在一個記錄學生成績的std::vector中,某些學生可能缺考:

std::vector<std::optional<int>> scores(10);
scores[3] = 85; // 學生3的成績

(三)避免空指針異常

在使用指針的場景中,std::optional可以替代指針來避免空指針異常。例如,在管理動態分配對象的生命周期時:

std::optional<std::unique_ptr<MyClass>> obj;
if (someCondition) {obj.emplace(std::make_unique<MyClass>());
}
if (obj) {obj->doSomething();
}

七、性能考慮

從性能角度來看,std::optional的實現是非常高效的。在大多數情況下,它的內存占用只比存儲的值多一個布爾標志位,用于表示值是否存在。這意味著在空間復雜度上,std::optional的額外開銷極小。

在時間復雜度方面,std::optional的構造和析構操作與普通對象的開銷相當。emplace方法更是直接在內部構造值,避免了不必要的拷貝和移動操作,進一步提高了效率。不過,在頻繁進行值的存在性檢查和訪問操作時,由于需要額外的條件判斷,可能會對性能產生一定的影響。但總體而言,與傳統的使用特殊標記值來處理可能缺失值的方式相比,std::optional在性能和安全性上都有顯著的優勢。

八、總結

std::optional是C++17標準庫中一個極具價值的特性,它為C++開發者提供了一種強大的工具,用于處理可能缺失值的情況。通過清晰地表達值的存在性,std::optional使得代碼更易于理解和維護,同時減少了因處理缺失值不當而引發的錯誤。無論是在函數返回值、容器元素,還是在避免空指針異常等場景中,std::optional都展現出了其獨特的優勢。在實際的C++17項目開發中,合理運用std::optional,能夠顯著提升代碼的質量和可靠性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/67252.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/67252.shtml
英文地址,請注明出處:http://en.pswp.cn/web/67252.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

擬合損失函數

文章目錄 擬合損失函數一、線性擬合1.1 介紹1.2 代碼可視化1.2.1 生成示例數據1.2.2 損失函數1.2.3 繪制三維圖像1.2.4 繪制等高線1.2.5 損失函數關于斜率的函數 二、 多變量擬合2.1 介紹2.2 代碼可視化2.2.1 生成示例數據2.2.2 損失函數2.2.3 繪制等高線 三、 多項式擬合3.1 介…

基于微信小程序的移動學習平臺的設計與實現(LW+源碼+講解)

專注于大學生項目實戰開發,講解,畢業答疑輔導&#xff0c;歡迎高校老師/同行前輩交流合作?。 技術范圍&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬蟲、數據可視化、安卓app、大數據、物聯網、機器學習等設計與開發。 主要內容&#xff1a;…

【公因數匹配——暴力、(質)因數分解、哈希】

題目 暴力代碼&#xff0c;Acwing 8/10&#xff0c;官網AC #include <bits/stdc.h> using namespace std; const int N 1e610; vector<int> nums[N]; int main() {ios::sync_with_stdio(0);cin.tie(0);int n;cin >> n;for(int i 1; i < n; i){int x;ci…

127周一復盤 (165)玩法與難度思考

1.上午測試&#xff0c;小改了點東西&#xff0c; 基本等于啥也沒干。 匆忙趕往車站。 從此進入春節期間&#xff0c;沒有開發&#xff0c;而思考與設計。 2.火車上思考玩法與難度的問題。 目前的主流作法實際上并不完全符合不同玩家的需求&#xff0c; 對這方面還是要有自…

【數據結構】_鏈表經典算法OJ(力扣版)

目錄 1. 移除鏈表元素 1.1 題目描述及鏈接 1.2 解題思路 1.3 程序 2. 反轉鏈表 2.1 題目描述及鏈接 2.2 解題思路 2.3 程序 3. 鏈表的中間結點 3.1 題目描述及鏈接 3.2 解題思路 3.3 程序 1. 移除鏈表元素 1.1 題目描述及鏈接 原題鏈接&#xff1a;203. 移除鏈表…

編譯器gcc/g++ --【Linux基礎開發工具】

文章目錄 一、背景知識二、gcc編譯選項1、預處理(進行宏替換)2、編譯&#xff08;生成匯編&#xff09;3、匯編&#xff08;生成機器可識別代碼&#xff09;4、鏈接&#xff08;生成可執行文件或庫文件&#xff09; 三、動態鏈接和靜態鏈接四、靜態庫和動態庫1、動靜態庫2、編譯…

Java 注解與元數據

Java學習資料 Java學習資料 Java學習資料 一、引言 在 Java 編程中&#xff0c;注解&#xff08;Annotation&#xff09;和元數據&#xff08;Metadata&#xff09;是兩個重要的概念。注解為程序提供了一種在代碼中嵌入額外信息的方式&#xff0c;這些額外信息就是元數據。元…

操作系統指定用戶密碼永不過期

背景 實際生產環境中&#xff0c;數據中心操作系統通常會有基線要求&#xff08;比如等保之類&#xff09;&#xff0c;要求設置操作系統密碼有效期&#xff0c;但是infra團隊或者操作系統管理員或者某些業務配置使用的操作系統用戶又需要密碼不能不停修改&#xff08;或者說一…

無用的知識又增加了:is_assignable means?

std::pair的默認operator被delete掉了&#xff0c;取而代之的是兩個enable_if版本。 為什么這么設計&#xff0c;我的理解是在std::map里&#xff0c;已經保存的元素的key值是不能被修改的&#xff0c;比如 注意&#xff0c;下面的代碼會修改key值&#xff0c;編譯時出現錯誤…

能量提升法三:贊美

前情回顧&#xff1a; 《能量提升法二&#xff1a;感恩》 片段&#xff1a;“感恩&#xff0c;就像是在跟世界說&#xff1a;謝謝你&#xff0c;我收到了&#xff0c;我很喜歡&#xff0c;請多來點” 把它歸還人海&#xff0c;就當作每一個人&#xff0c;都有可能是曾經幫助…

25美賽ABCDEF題詳細建模過程+可視化圖表+參考論文+寫作模版+數據預處理

詳情見該鏈接&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 25美國大學生數學建模如何準備&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;-CSDN博客文章瀏覽閱讀791次&#xff0c;點贊13次&#xff0c;收藏7次。通過了解比賽基本…

2025企業繁體鏡像站鏡像站群版 | 干擾碼+拼音插入

技術背景 高效的SEO優化和內容采集是企業站群系統的核心競爭力。本文將詳細介紹一套企業級網站鏡像工具包&#xff0c;重點展示其在SEO優化、內容采集、智能處理等方面的創新實現。 系統特性 1. SEO優化功能 關鍵詞智能布局標題標簽優化鏈接結構優化移動端適配頁面加速優化…

動態規劃<九>兩個數組的dp

目錄 引例 LeetCode經典OJ題 1.第一題 2.第二題 3.第三題 4.第四題 5.第五題 6.第六題 7.第七題 引例 OJ傳送門LeetCode<1143>最長公共子序列 畫圖分析&#xff1a; 使用動態規劃解決 1.狀態表示 ------經驗題目要求 經驗為選取第一個字符串的[0,i]區間以及第二個字…

大數據學習之SCALA分布式語言三

7.集合類 111.可變set一 112.可變set二 113.不可變MAP集合一 114.不可變MAP集合二 115.不可變MAP集合三 116.可變map一 package com . itbaizhan . chapter07 //TODO 2. 使用 mutable.Map 前導入如下包 import scala . collection . mutable // 可變 Map 集合 object Ma…

MongoDB中常用的幾種高可用技術方案及優缺點

MongoDB 的高可用性方案主要依賴于其內置的 副本集 (Replica Set) 和 Sharding 機制。下面是一些常見的高可用性技術方案&#xff1a; 1. 副本集 (Replica Set) 副本集是 MongoDB 提供的主要高可用性解決方案&#xff0c;確保數據在多個節點之間的冗余存儲和自動故障恢復。副…

基于OSAL的嵌入式裸機事件驅動框架——整體架構調度機制

參考B站up主【架構分析】嵌入式祼機事件驅動框架 感謝大佬分享 任務ID &#xff1a; TASK_XXX TASK_XXX 在系統中每個任務的ID是唯一的&#xff0c;范圍是 0 to 0xFFFE&#xff0c;0xFFFF保留為SYS_TSK_INIT。 同時任務ID的大小也充當任務調度的優先級&#xff0c;ID越大&#…

WGCLOUD運維工具從入門到精通 - 如何設置主題背景

需要升級到WGCLOUD的v3.5.7或者以上版本&#xff0c;才會支持自定義設置主題背景色 WGCLOUD下載&#xff1a;www.wgstart.com 我們登錄后&#xff0c;在右上角點擊如下的小圖標&#xff0c;就可以設置主題背景色了&#xff0c;包括&#xff1a;經典白&#xff08;默認&#x…

LigerUI在MVC模式下的響應原則

LigerUI是基于jQuery的UI框架&#xff0c;故他也是遵守jQuery的開發模式&#xff0c;但是也具有其特色的偵聽函數&#xff0c;那么當LigerUI作為View層的時候&#xff0c;他所發送后端的必然是表單的數據&#xff0c;在此我們以倆個div為例&#xff1a; {Layout "~/View…

基于RIP的MGRE VPN綜合實驗

實驗拓撲 實驗需求 1、R5為ISP&#xff0c;只能進行IP地址配置&#xff0c;其所有地址均配為公有IP地址&#xff1b; 2、R1和R5間使用PPP的PAP認證&#xff0c;R5為主認證方&#xff1b; R2與R5之間使用ppp的CHAP認證&#xff0c;R5為主認證方&#xff1b; R3與R5之間使用HDLC封…

git的理解與使用

本地的git git除了最經典的add commit push用來做版本管理&#xff0c;其實他的分支管理也非常強大 可以說你學好了分支管理&#xff0c;就可以完成團隊的配合協作了 git倉庫 我們可以使用git init來初始化一個git倉庫&#xff0c;只要能看見.git文件夾&#xff0c;就代表這…