針對回傳值做判斷的新語法:if statement with initializer

| | 0 Comments| 10:35|
Categories:

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::stringmData = { ...};

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;
  }
}

基本上,也是一樣只是方便控制物件的生命週期了~


參考:

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *