C++ 17 新增三個標準屬性(Attribute):
- [[fallthrough]] 用以標記接續執行下一個 switch case 的意圖。
- [[nodiscard]] 用以提醒函式呼叫者檢查回傳值。
- [[maybe_unused]] 用以標記可能不會被使用的變數宣告。
本文會依序介紹這三個屬性。
[[fallthrough]]
在 C/C++ 語言中,switch 述句通常會有若干個 case 與一個 default。在進入一個 case 或 default 之後,如果沒有以 break 述句或其他述句跳出 switch 述句,程式會繼續執行接續的 case 或 default:
#include <iostream>
int main() {
int x = 0;
std::cin >> x;
int result = 0;
switch (x) {
case 2:
result++;
case 0:
result++;
default:
result++;
}
std::cout << result << std::endl;
}
舉例來說,如果上面的 x 等於 2,進入 case 2 之後會一路執行 case 2 的 result++、case 0 的 result++ 與 default 的 result++。
然而這常是程式錯誤或安全漏洞的源頭,因此許多 C++ 編譯器會提供額外的警告選項。如果我們使用的編譯器是 GCC 或者 Clang,我們能以 -Wimplicit-fallthrough 開啟警告:
$ g++ -std=c++17 fallthrough_example.cpp -Wimplicit-fallthrough
fallthrough_example.cpp: In function ‘int main()’:
fallthrough_example.cpp:10:17: warning: this statement may fall through
[-Wimplicit-fallthrough=]
result++;
~~~~~~^~
fallthrough_example.cpp:11:9: note: here
case 0:
^~~~
fallthrough_example.cpp:12:17: warning: this statement may fall through
[-Wimplicit-fallthrough=]
result++;
~~~~~~^~
fallthrough_example.cpp:13:9: note: here
default:
^~~~~~~
如果這段程式的確需要接續執行之後的 case 或 default,我們能加上 [[fallthrough]] 屬性以關閉警告:
#include <iostream>
int main() {
int x = 0;
std::cin >> x;
int result = 0;
switch (x) {
case 2:
result++;
[[fallthrough]]; // Added
case 0:
result++;
[[fallthrough]]; // Added
default:
result++;
}
std::cout << result << std::endl;
}
[[fallthrough]] 屬性必須加在「此函式中能從此 case(或 default)跳至下一個 case(或 default)」的位置。在程式碼中 [[fallthrough]] 屬性與下一個 case(或 default)不一定是相鄰的兩列。我們可以看另一個複雜的例子:
#include <iostream>
int main() {
int x = 0;
int y = 0;
std::cin >> x >> y;
int result = 0;
switch (x) {
case 2:
if (y % 2 == 0) {
result++;
[[fallthrough]]; // #1
} else {
break; // #2
}
default:
result++;
}
std::cout << result << std::endl;
}
在上面的例子裡,我們在 #1 加上 [[fallthrough]] 以明確表明繼續執行 default 的意圖。而在 #2 我們已經用 break 述句離開 switch 述句,因此我們不用加上 [[fallthrough]]。
[[nodiscard]]
在一些情況下,函式的編寫者會想要「提醒」函式的呼叫者必須檢查或使用函式的回傳值。C 語言的 scanf 函式就是典型的例子:
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x);
}
如果 scanf(x) 發生錯誤,x 可能會是未定義的數值。如果我們沒有檢查 scanf() 的回傳值,後續的計算都會有問題。
作為函式的編寫者,我們能以 [[nodiscard]] 提醒函式呼叫者檢查回傳值:
#include <stdio.h>
[[nodiscard]] int read_int(int &value) {
return scanf("%d", &value);
}
int main() {
int x, y, z;
read_int(x); // #1: Trigger warning
if (read_int(y)) { // #2: Properly checked
printf("%d\n", y);
}
(void)read_int(z); // #3: Explicitly discarded
}
在上面的範例中,我們忽略第一個函式呼叫的回傳值(#1),因此編譯器會印出警告訊息。第二個函式呼叫(#2)展示了正確的使用方法。如果我們很清楚的知道我們要捨棄 [[nodiscard]] 函式的回傳值,我們能用 (void) 轉型關閉 [[nodiscard]] 警告。
[[nodiscard]] 屬性也能套用於 struct、class 或 enum 上。如果一個 struct、class 或 enum 帶有 [[nodiscard]] 屬性,且它是一個函式的回傳型別,則該函式視同帶有 [[nodiscard]] 屬性。例如:
#include <stdio.h>
enum [[nodiscard]] ErrorCode {
OK,
IO_ERROR,
};
ErrorCode read_int(int &value) {
return scanf("%d", &value) == 1 ? OK : IO_ERROR;
}
int main() {
int x;
read_int(x);
}
上述範例中,雖然 read_int 函式本身沒有 [[nodiscard]] 屬性,但是因為它的回傳型別 ErrorCode 有 [[nodiscard]] 屬性,所以編譯器會為 read_int(x) 印出警告訊息。
[[maybe_unused]]
許多 C/C++ 編譯器能幫我們找出「未使用變數」,並印出警告訊息,讓我們清理這些非必要的程式碼。
然而,在一些情況下,這些「未使用變數」並不是真的沒用。舉例來說,有時候使用這些變數的程式碼是被 #ifdef 與 #endif 包起來,在特定組態下才會發生作用。例如:
#include <iostream>
int test(int a, int b, int c) {
int result = a + b;
#ifdef ENABLE_FEATURE_C
result += c;
#endif
return result;
}
int main() {
std::cout << test(1, 2, 3) << std::endl;
}
如果我們以 -Wunused 與 -Wunused-parameter 編譯,我們會看到以下警告訊息:
$ g++ -std=c++17 maybe_unused.cpp -Wunused -Wunused-parameter
maybe_unused.cpp: In function ‘int test(int, int, int)’:
maybe_unused.cpp:3:28: warning: unused parameter ‘c’ [-Wunused-parameter]
int test(int a, int b, int c) {
^
如果想要關閉警告訊息,我們能加上 [[maybe_unused]] 屬性:
#include <iostream>
int test(int a, int b, [[maybe_unused]] int c) { // Modified
int result = a + b;
#ifdef ENABLE_FEATURE_C
result += c;
#endif
return result;
}
int main() {
std::cout << test(1, 2, 3) << std::endl;
}





