if 這個條件判斷的語法,應該算是 C / C++ 語言的基礎,一般人應該也都會使用。
而如果是要函式的回傳值作條件判斷的時候,假如之後還會用到這個回傳值,通常就得寫成類似下面的形式:
std::string getText(int i) { static std::map<int, std::string> mData = { {1, "value 1"}, {2, "value 2"}, }; auto iter = mData.find(i); if (iter == mData.end()) return ""; else return iter->second; }
其實這樣寫並沒有什麼太大的問題,大家也都很習慣了。
不過在 C++17 的時候其實有針對 if 和 switch 加入「Init-statements for if and switch」(P0305R1)這個語法,可以把上面的程式改寫成:
if (auto iter = mData.find(i); iter == mData.end()) return ""; else return iter->second;
這邊的語法基本上是:
if (init; condition) { }
也就是允許在本來的條件判斷式前面,加入變數的初始化。
在程式上其實並沒有簡化,但是寫成這樣和本來的寫法還是有差別的,那就是在 init;
階段建立的變數的生命週期只有在這個 if-else 的範圍內,離開 if-else 就消失了。
也就是說,他的語法相當於寫成:
{ init; if (condition) { } }
基本上,就是幫忙在外面加上一層大括號、來限制物件的生命週期了~而雖然好像算是很簡單的簡化、但是這樣的設計在某些情況下確實會方便一點。
比如果在 C++17 之前,如果要呼叫 getText()
兩次的話,大概會寫成:
std::string s = getText(1); if (!s.empty()) std::cout << s << std::endl; std::string s2 = getText(3); if (!s2.empty()) std::cout << s2 << std::endl;
這邊的寫法會為了避免變數名稱衝突,所以得改用 s2
這個變數,但是如果是複製貼上的修改的話,其實也很有可能會出現某些變數名稱忘了改到、而繼續使用 s
的狀況,在某些狀況下,這樣的問題也很難找…
如果想要限制 s
的使用、同時避免變數衝突的話,這邊可以加上 {}
、寫成:
{ std::string s = getText(1); if (!s.empty()) std::cout << s << std::endl; } { std::string s = getText(3); if (!s.empty()) std::cout << s << std::endl; }
這樣也算滿簡單的、同時可以避免不少問題。但是如果是在 Visual Studio 這類有把大括號摺疊起來的功能的話,有時候會覺得這種為了限制生命週期而加入的大括號會有點麻煩。
如果改用 C++17 這種新的 if 的寫法的話,則可以改成:
if (std::string s = getText(1); !s.empty()) std::cout << s << std::endl; if (std::string s = getText(3); !s.empty()) std::cout << s << std::endl;
這樣的寫法改動相對更少,而且程式碼在某方面來說也會簡潔一點。
如果是搭配 structured binding 使用的話,在遇到函式回傳 std::pair<>
或 std::tuple<>
這類實際上是多變數的型別的時候,也是可以滿方便的。
像是要判斷 map 的 inster()
到底有沒有建立新的值、就可以寫成:
std::map<int, std::string> mData = { ...}; if (auto [it, inserted] = mData.insert({ 3, "hello" }); !inserted) std::cout << "already exists with value " << it->second << "\n";
另一種 Heresy 個人覺得滿實用的情境,是在使用 mutex / lock 的時候(參考)。
比如說:
if (std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock); lock.owns_lock()) { std::cout << "successfully locked the resource\n"; } else { std::cout << "resource not currently available\n"; }
這樣的寫法可以不需要另外加上額外的大括號、就明確地把 lock
的生命週期限制在 if-else 中,等到判斷式都跑完了,就會自動釋放。
另外,前面也有提到,實際上這個語法也可以用在 switch-case 上。比如說:
#include <iostream> #include <map> #include <string> std::pair<int, std::string> httpRequest(std::string_view url); int main() { switch (auto [code, text] = httpRequest("http://127.0.0.1"); code) { case 200: std::cout << text << "\n"; break; case 404: std::cout << "File not found\n"; break; } }
基本上,也是一樣只是方便控制物件的生命週期了~
參考: