Add "[C++ 筆記] 好用的 optional"
This commit is contained in:
130
content/posts/2019/[C++ 筆記] 好用的 optional/index.md
Normal file
130
content/posts/2019/[C++ 筆記] 好用的 optional/index.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
slug: "[C++ 筆記] 好用的 std::optional"
|
||||||
|
title: "[C++ 筆記] 好用的 std::optional"
|
||||||
|
description:
|
||||||
|
toc: true
|
||||||
|
authors:
|
||||||
|
- awin
|
||||||
|
tags:
|
||||||
|
- c++
|
||||||
|
categories:
|
||||||
|
- Programming
|
||||||
|
series:
|
||||||
|
- C++ 筆記
|
||||||
|
date: 2019-12-22T00:00:00
|
||||||
|
lastmod: 2019-12-22T00:00:00
|
||||||
|
featuredVideo:
|
||||||
|
featuredImage:
|
||||||
|
draft: false
|
||||||
|
enableComment: true
|
||||||
|
---
|
||||||
|
|
||||||
|
> since C++17
|
||||||
|
|
||||||
|
[`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) 讓 function 的回傳值多了一個選擇:**有值**或是 `nullopt`。
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
例如我們有一個 function 要從某個檔案讀值出來(或是去 DB 查一個東西之類的需求),就假設我們要讀一個 int,我們也許會這樣寫:
|
||||||
|
```cpp
|
||||||
|
int readData(std::string filePath) {
|
||||||
|
...
|
||||||
|
int data = readFromFile(...);
|
||||||
|
...
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
但如果要讀的檔案不存在呢?這時候要 return 什麼呢?所以這時候我們會改成這樣寫:
|
||||||
|
```cpp
|
||||||
|
bool readData(std::string filePath, int& readData) {
|
||||||
|
FILE* file = fopen(...)
|
||||||
|
if (!file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
readData = readFromFile(...);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
我們用一個回傳值代表檔案讀取失敗與否,要是檔案存在且開啟成功,那麼 `readData` 這個回傳值就代表我們讀到的值。要是回傳值是 `false`,那 `readData` 就沒有意義。
|
||||||
|
但是這樣會讓傳入跟傳出的參數混在一起,第一時間也不容易直覺的分辨出來,像這種情況 Python 就清楚很多:
|
||||||
|
```python
|
||||||
|
def readData(filePath):
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
with open(filePath, "rb") as f:
|
||||||
|
data = f.read(...)
|
||||||
|
return data
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
value = readData("123.txt")
|
||||||
|
if value:
|
||||||
|
pass # Do something...
|
||||||
|
```
|
||||||
|
可以像 Python 那樣都從回傳值來判斷嗎? `std::optional` 可以達到這個目的:
|
||||||
|
```cpp
|
||||||
|
std::optional<int> readData(std::string filePath) {
|
||||||
|
FILE* file = nullptr;
|
||||||
|
fopen_s(&file, filePath.c_str(), "rb");
|
||||||
|
if (!file) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int readData;
|
||||||
|
fread(&readData, 1, sizeof(readData), file);
|
||||||
|
fclose(file)
|
||||||
|
|
||||||
|
return readData;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = readData("123.txt");
|
||||||
|
if (result) {
|
||||||
|
auto value = result.value();
|
||||||
|
// Use value here...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 或是這樣寫也可以
|
||||||
|
if (result == std::nullopt) {
|
||||||
|
// Error handle here
|
||||||
|
} else {
|
||||||
|
auto value = result.value();
|
||||||
|
// Use value here...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
雖然用起來沒辦法讓 C++ 像 Python 那麼簡單,但是 `std::optional` 確實讓整段 code 看起來更清楚了。
|
||||||
|
除了 [`std::optional<T>::value()`](https://en.cppreference.com/w/cpp/utility/optional/value) 以外,還有其他的取值方法:
|
||||||
|
```cpp
|
||||||
|
std::optional<std::string> name;
|
||||||
|
|
||||||
|
// Some process...
|
||||||
|
|
||||||
|
if (name)
|
||||||
|
{
|
||||||
|
printf("name = %s\n", (*name).c_str());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 `std::optional` 包含的是 struct 或是 class,也可以用 `->` 來直接存取 member(或 member function):
|
||||||
|
```cpp
|
||||||
|
struct User {
|
||||||
|
uint32_t id;
|
||||||
|
std::string name;
|
||||||
|
int32_t age;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<User> user;
|
||||||
|
|
||||||
|
// Some process...
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
printf("id = %d\n", user->id);
|
||||||
|
printf("name = %s\n", user->name.c_str());
|
||||||
|
printf("age = %d\n", user->age);
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user