2011 年8 月,ISO 委員會發布了 C++11,2017 年 12 月又發布了 C++17 標準,每次編程語言新版本的迭代,會令不少團隊也開始著手升級開發環境,例如本文作者。那么從 C++11 升級到 C++17,究竟有哪些特別的變化值得關注?
最近,我們團隊正在升級開發環境,嘗試使用許多工具和編程語言的新版本。在這個過程中,比較困難的一項工作是將我們的嵌入式應用程序的代碼庫從 C++11 升級到 C++17。
在本文中,我將展示在嵌入式世界中非常有用的一些C++17 的特性(注意:從 C++11 遷移到 C++17 也涵蓋了 C++14,因此我也會提到 C++14 的一些特性)。
查看完整的C++17特性列表,可前往:https://github.com/AnthonyCalandra/modern-cpp-features#c17-language-features。
C++14 的主要變化
當初,我們從 C++03 遷移到了 C++11,與之相比,從 C++11 升級到 C++14 時看到的升級比較小。因此,可以在嵌入式系統中使用的 C++14 特有功能實際上并不多。
二進制字面量
如果你經常需要執行按位運算或修改寄存器,那么一定很喜歡這些字面量。一些編譯器具有支持此類字面量的擴展,這些字面量在實際的標準中也有一席之地。
uint8_t a = 0b110; // == 6
uint8_t b = 0b1111'1111; // == 255
constexpr**
在 C++14 中,可以在 constexpr 函數中使用的語法得到了擴展。constexpr 特別適用于嵌入式開發,因為它可以在編譯時進行計算并將一些代碼簡化為常量。請注意,只有當表達式的所有需求都可以在編譯期間確定時,才能在編譯時計算表達式。
constexpr int factorial(int n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
factorial(5); // == 120 (Calculated at compile time)
C++ 17 的世界
與 C++14 相比,C++17 標準有了很大的變化,但無需擔心,你仍然可以使用已有的功能。除了已有功能之外,你還將擁有更強大的 C++17 語法和庫。
(1)屬性
首先,我們來介紹三個新屬性:[[fallthrough]]、[[nodiscard]] 和 [[maybe_unused]]。因為這些屬性只在編譯時考慮,所以你根本不需要擔心它們的效率。它們的存在就是為了提升代碼開發。
[[fallthrough]]
你可以利用這個屬性將兩個相鄰的 case 分支的主體合并到一個 switch 中,而不會收到來自編譯器的任何警告。你可以通過這個屬性告訴編譯器前一個case主體結束是有意為之。
switch (n) {
case 1: [[fallthrough]]
// ...
// no `break;`
case 2:
// ...
break;
}
[[nodiscard]]
你是不是也經常忘記檢查函數的返回值?有了這個屬性,丟棄返回值就會收到編譯器的警告。
[[nodiscard]] bool do_something() {
return is_success; // true for success, false for failure
}
do_something(); /* warning: ignoring the return value of function declared with attribute 'nodiscard' */
[maybe_unused]]
為了避免收到警告,必須將未使用的變量轉換為 void,你是不是也感到不耐煩?試試看這個屬性,你就可以擺脫那些煩人的警告。
void my_callback(std::string msg, [[maybe_unused]] bool error) {
// Don't care if `msg` is an error message, just log it.
log(msg);
}
(2)編譯時的力量
編譯時的檢查是我最喜歡 C++ 的地方。在 C++17 中,這種能力通過一些新特性得到進一步增強。想一想許多嵌入式系統中繁瑣的調試過程,如今甚至不需要部署代碼就可以檢查結果,是不是覺得是個特大好消息?傳輸可執行文件、準備環境和測試等一系列工作都非常艱巨,而且很耗時。但使用編譯時編程,這部分頭疼的工作都可以省略。
沒有消息的靜態斷言
你可能認為,我們已經有了 static_assert(..),可以在編譯時進行檢查。而如今,斷言機制甚至不需要錯誤消息。這樣,代碼看上去會更加清晰。
static_assert(false);
if constexpr
我最喜歡的一個語句!我們可以利用 if constexpr 編寫一些代碼,這些代碼可以根據編譯時的條件,有選擇地進行實例化。
template<typename T>
auto length(const T& value) noexcept {
if constexpr (std::integral::value) { // is number
return value;
}
else {
return value.length();
}
}
int main() noexcept {
int a = 5;
std::string b = "foo";
std::cout << length(a) << ' ' << length(b) << '
'; // Prints "5 3"
}
在 C++17 之前,上面這段代碼需要編寫兩個不同的函數,分別用于字符串和整數輸入,如下所示。
int length(const int& value) noexcept {
return value;
}
std::size_t length(const std::string& value) noexcept {
return value.length();
constexpr lambda
如果你也喜歡在代碼中使用 lambda 表達式,那么肯定會喜歡這個功能。此外,Lambdas 的調用也可以采用直接聲明為 constexpr 的形式。
auto identity = [](int n) constexpr { return n; };
static_assert(identity(123) == 123);
(3)語法糖
在 C++17 中,有一些功能可以幫助你編寫更漂亮的代碼。即使它們的存在對運行時性能沒有明顯的影響,但你會很喜歡它們。
折疊表達式
如果你有過使用可變參數模板來編寫具有可變輸入或迭代次數的遞歸算法的經歷,那么就可能遇到必須為該可變參數模板函數實現終止符的問題。例如,下面的代碼是用 C++11 編寫的,作用是累加給定的數字。
int sum() { return 0; } // Termination function
template<typename ...Args>
int sum(const int& arg, Args... args) {
return arg + sum(args...);
}
如果我們沒有實現不接受任何輸入的終止符,這段代碼將無法通過編譯。但有了折疊表達式,你就不必實現終止符了,而代碼看上去也更好,如下所示。
template<typename ...Args>
int sum(Args&&... args) {
return (args + ...);
}
嵌套命名空間
不知道為什么 C++ 委員會以前沒有想到這一點。無需多說,分別看下面 C++11 和 C++17 中嵌套命名空間的定義,你就能發現區別。
// C++11
namespace A {
namespace B {
namespace C {
int i;
}
}
}
// C++17
namespace A::B::C {
int i;
}
加強版的條件語句
如果所有條件語句都像 for 語句一樣具有初始化,那是不是更強大?在 C++17 中,條件語句也增加了初始化部分。
這是迄今為止我所見過的最強大的功能之一,因為你無需在輸入一系列 if-else 語句或 switch-case 之前,編寫一堆局部變量。
if (int i = 4; i % 2 == 0) {
cout << i << " is even number" << endl;
}
switch (int i = rand() % 100; i) {
default:
cout << "i = " << i << endl;
break;
}
內聯變量
在 C++17 之前,我們必須在源文件中實例化類內靜態變量。如今,你可以使用內聯變量將聲明和初始賦值合并到類定義中,如下所示。
struct BabaMrb {
static const int value = 10;
static inline std::string className = "Hello Class";
}
(4)其他特性
C++17 中還有許多我不知道如何歸類的的其他特性。下面,我們來逐一介紹。
復制省略
復制省略(Copy elision),即返回值優化,是大多數編譯器為防止在某些情況下出現額外副本而實現的優化。從 C++17 開始,直接返回對象時必然會觸發復制省略。在某些情況下,即使只有一次復制操作也會影響系統的性能,例如對實時性有嚴格要求的系統。遇到這種情況,我們最好確保避免復制,以免降低系統性能。
struct C {
C() { std::cout << "Default constructor" << std::endl; }
C(const C&) { std::cout << "Copy constructor" << std::endl; }
};
C f() {
return C(); // Definitely performs copy elision
}
C g() {
C c;
return c; // May perform copy elision
}
int main() {
C obj = f(); // Copy constructor isn't called
}
共享互斥鎖
在使用共享互斥鎖后,我們就可以按需讀取對象而無需加鎖,而寫調用可以照往常一樣使用常規互斥鎖來鎖定對象。共享互斥鎖可以加快只讀訪問操作的速度,因為讀取操作可以同步進行。
硬件干涉大小
這個新的庫功能可以幫助你在編譯期間確定 L1 緩存行的大小。有了這個功能,你就可以根據 L1 緩存行的大小調整結構、緩沖區等。我在使用 C++11 為 ARM Cortex-A9 內核實現低級裸機 DMA 驅動程序時就會用到這個功能,因為在編寫這些代碼時,我需要手動管理高速緩存和主內存之間的一致性。
盡管此功能非常強大,但直到版本 12 才在所有版本的 GCC 中實現,因此很可能你當前的編譯器并不支持。如下代碼是一個示例,可以幫助你更好地理解這個功能。
using std::hardware_constructive_interference_size;
using std::hardware_destructive_interference_size;
// 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ...
constexpr std::size_t hardware_constructive_interference_size = 64;
constexpr std::size_t hardware_destructive_interference_size = 64;
struct alignas(hardware_constructive_interference_size) OneCacheLiner { // occupies one cache line
std::atomic_uint64_t x{};
std::atomic_uint64_t y{};
};
總結
與 C++14 不同,C++17 引入了許多新特性。其中一些功能對嵌入式系統開發非常有幫助。
不同產品之間,嵌入式設備的計算能力差異很大。由于 CPU 性能、缺乏編譯器支持、驗證必要性等多種原因,我選擇的某些功能可能不適用于你的固件??傮w而言,遷移到 C++17 可能需要花費大量的時間和精力,請認真考慮是否需要遷移。
-
嵌入式系統
+關注
關注
41文章
3593瀏覽量
129523 -
C++
+關注
關注
22文章
2110瀏覽量
73687 -
編譯器
+關注
關注
1文章
1634瀏覽量
49153
原文標題:從 C++11 升級至 C++17,它們讓嵌入式系統更好了!
文章出處:【微信號:技術讓夢想更偉大,微信公眾號:技術讓夢想更偉大】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論