引言
在閱讀開源項目源代碼是,發現了一個有趣且特殊的C++特性:屬性。
屬性(attribute specifier sequences)是在C++11標準引入的。在C++11之前,編譯器特有的擴展被廣泛用來提供額外的代碼信息。例如,GNU編譯器(GCC)使用__attribute__
來控制函數的行為。但是缺點也很明顯,那就是這種方式缺乏標準化,不同編譯器的特性并不兼容,嚴重影響了代碼的可移植性。
為了解決這一問題,C++11標準引入了屬性,提供一種標準化的方法來給編譯器額外的信息。屬性既可以應用于類型聲明,也可以應用于聲明語句、定義、代碼塊等,為編譯器提供了關于編碼意圖的附加信息。
屬性的作用
屬性引入C++后,提供了以下幾個方面的價值:
-
可移植性:提供了一種標準的方式來傳達編譯器特定的信息,減少了依賴特定編譯器擴展的需要。
-
可讀性:通過標準的屬性,代碼的特定行為和意圖被清晰地表達,易于他人理解。
-
健壯性:屬性可以幫助檢測潛在的錯誤,如忽略了重要的函數返回值。
-
性能:許多屬性可以幫助編譯器進行優化,提高程序性能。
常見屬性
以下是C++中一些常用的屬性:
-
[[noreturn]]
- 表明函數不會返回到調用者。這通常用于那些通過拋出異常或終止程序來退出的函數。 -
[[nodiscard]]
- 用于函數或類型,標明調用者不應忽略返回值。對于類型,它表示該類型的對象或其構造函數返回的對象不應被忽略。 -
[[deprecated]]
- 標記某個實體(如函數、類、類型別名、變量等)為過時的,建議不要使用。可選地,可以提供一條消息說明替代的使用方式。 -
[[fallthrough]]
- 在C++17中引入,用于switch語句的case節中,明確表示允許控制流從當前case分支無條件跳轉到下一個case分支的行為,避免編譯器生成可能的警告。 -
[[maybe_unused]]
- 表示某個實體(如函數、類、變量等)可能不會被使用,從而防止編譯器發出未使用警告。 -
[[likely]]
和[[unlikely]]
- 這兩個屬性是在C++20中引入的,用來顯式地指示給定的布爾表達式結果的可能性。[[likely]]
表示表達式結果為true的可能性更高,而[[unlikely]]
表示結果為false的可能性更高。 -
[[no_unique_address]]
- 在C++20中引入,它指示類成員不必擁有唯一的地址,允許空基優化或類似的優化技術。
這些屬性可以用來改善代碼的文檔化、提高性能、幫助編譯器檢查錯誤和警告,以及啟用或禁用特定的編譯器行為。
它們是現代C++編程中代碼質量和維護方面的重要工具。不同的編譯器可能對屬性的支持程度有所不同。
常用屬性示例
[[noreturn]]
[[noreturn]]
屬性指示某個函數不會返回到調用者。這通常用于那些通過拋出異常或終止程序來退出的函數。
[[noreturn]] void terminate_program() {// ... 清理操作 ...std::exit(1); // std::exit不返回
}
[[nodiscard]]
[[nodiscard]]
屬性用于函數或類型,標明調用者不應忽略返回值。這有助于防止編碼中的錯誤,例如忘記處理函數返回的錯誤代碼。
[[nodiscard]] int compute() {// ... 計算操作 ...return result;
}void example() {compute(); // 如果忽略了結果,編譯器可能會警告
}
C++20對nodiscard進行了擴展,支持注明原因。格式為:[[nodiscard(“reason”)]]
[[deprecated]]
[[deprecated]]
屬性用于標記過時的實體。
[[deprecated("Use new_function instead")]]
void old_function() {// ...
}void example() {old_function(); // 使用這個函數時,編譯器會給出警告
}
[[fallthrough]]
[[fallthrough]]
屬性用在switch
語句中,表示有意識的case穿透。
void example(int val) {switch (val) {case 1:// 計算某些事情[[fallthrough]]; // 明確表明穿透是有意為之case 2:// 1和2執行相同的代碼break;default:// 其他值處理break;}
}
[[maybe_unused]]
[[maybe_unused]]
屬性用于可能不使用的變量或函數,防止編譯器發出未使用警告。
[[maybe_unused]] static bool is_debug = true;void example() {[[maybe_unused]] int local_variable = compute();
}
[[likely]] 和 [[unlikely]]
[[likely]]
和[[unlikely]]
在C++20中引入,幫助編譯器優化分支預測。
bool condition = /* ... */;
if ([[likely]] condition) {// 大多數情況下,條件為真時的代碼
} else {// 條件為假時的代碼
}
其他不常用的屬性介紹
[[carries_dependency]] (C++11)
[[carries_dependency]]
屬性用來指示在函數參數或返回值中的依賴關系鏈可以傳遞,這在使用std::memory_order
時或在多線程編程中非常重要。
#include <atomic>
#include <iostream>std::atomic<int> global_data;// 該函數表明參數和返回值帶有依賴關系鏈
[[carries_dependency]] int load_data(std::atomic<int>& data) {return data.load(std::memory_order_consume);
}void process_data() {// 從全局數據載入的依賴關系被保留int local_data = load_data(global_data);// 接下來的操作可以依賴于這個順序
}
[[no_unique_address]] (C++20)
[[no_unique_address]]
屬性表示非靜態數據成員不必具有唯一的地址,這允許編譯器在可能的情況下進行內存優化。在C++中,即使是完全空的類(不含任何成員變量或成員函數)也至少會占用1字節的大小,這是為了確保每個對象都有一個唯一的地址。但是,有時候這個額外的1字節并不是必須的,例如當空類作為其他類的成員時,這在包含多個空類成員的大型結構體中可以節省大量內存。
struct Empty {}; // empty classstruct X
{int i;Empty e;
};struct Y
{int i;[[no_unique_address]] Empty e;
};int main()
{// the size of any object of empty class type is at least 1static_assert(sizeof(Empty) >= 1); // at least one more byte is needed to give e a unique addressstatic_assert(sizeof(X) >= sizeof(int) + 1); static_assert(sizeof(Y) == sizeof(int));
}
[[assume(expression)]] (C++23)
[[assume(expression)]]
屬性告訴編譯器,在給定的點上,表達式總是評估為真。這有助于編譯器進行優化。
void process_data(int* ptr) {// 告訴編譯器ptr不為nullptr,這有助于優化[[assume(ptr != nullptr)]]*ptr = 42;
}
[[indeterminate]] (C++26)
[[indeterminate]]
屬性是一個假設的屬性,用來指明一個對象如果沒有被初始化,則具有不確定的值。由于C++26還目前還未發布,暫時用不了。
void f(int);
void g()
{int x [[indeterminate]]; // indeterminate valueint y; // erroneous valuef(x); // 允許編譯通過,但是具有不確定的行為f(y); // 編譯報錯,不允許使用未初始化的值
}
[[optimize_for_synchronized]] (TM TS)
[[optimize_for_synchronized]]
屬性來自事務性內存技術規范(TM TS),它建議函數應該為同步塊中的調用進行優化。TM TS不是ISO C++標準的一部分,支持度因編譯器而異。
// 這個屬性建議函數在同步塊中調用時應該進行優化
[[optimize_for_synchronized]] void synchronized_function() {// 在同步語句中頻繁調用的函數
}
結語
筆者最喜歡的C++屬性就是[[nodiscard]]了,計劃今天就在團隊中推廣開。因為許多開發者在調用一些可能失敗的函數不檢查返回值,導致代碼魯棒性較低。給一些重要函數加上[[nodiscard]]屬性之后,編譯器就能避免這種情況的發生,真是太有用了。試了一下,MSVC下nodiscard會出現警告,還好我們開了警告視為錯誤,那么就可以確保開發者不會忘記處理返回值了。
如果向了解更多關于C++屬性的知識,那么可以來cppreference看看。cppreference的C++的屬性參考:Attribute specifier sequence(since C++11)