C++17新屬性詳解
C++迭代速度相對來說還是比較慢的,2010年以後,C++的新版本迭代速度有所加快,這一點,從C++標準版本的歷史釋出圖1就可以看出來:
圖1 C++正式版本釋出歷史
C++11算是更新比較大的一次了,引入了很多新屬性,以至於C++11出來以後,好多C++同行感嘆,這看了C++11感覺像是在學習一門新語言!哈哈,這可能跟C++屬性眾多和庫眾多有關吧。現在我們來看看C++17的新增屬性吧。
C++17的入選特性有:
(1).非型別模板引數的 auto
模板引數分為兩種,一種是型別模板引數,也是我們用得最多的一種:
template <typename T, typename U>
auto add(T t, U u) {
return t+u;
}
裡面的 T
和 U
都是型別模板引數。另一種是非型別模板引數,它可以讓不同的字面量成為模板的引數:
template <typename T, int BufSize> class buffer_t { public: T& alloc(); void free(T& item); private: T data[BufSize]; } buffer_t<int, 100> buf; // 100 作為模板引數
遺憾的是我們在編寫模板的時候就必須明確非型別模板引數的具體型別,C++17 打破了這一限制,讓我們能夠在非型別模板引數中使用 auto 關鍵字,從而讓編譯器推導具體的型別:
template <auto value> void foo() {
return;
}
foo<10>(); // value 被推導為 int 型別
(2).std::variant<>
熟悉 boost 的人應該很早就聽說過 variant<> 了。variant<> 可以用於儲存和操作不同型別的物件。我們在前面(對標準庫的擴充:新增容器)對於迭代 std::tuple 時,簡單使用了 boost::variant<>。提供給 variant<> 的型別模板引數可以讓一個 variant<> 從而容納提供的幾種型別的變數(在其他語言(例如 Python/JavaScript 等)表現為動態型別)。
C++17 正式將 variant<> 納入標準庫,搖身一變成為 std::variant<>,有了它之後,我們可以將前面的程式碼更改為:
#include <variant>
template <size_t n, typename... Args>
std::variant<Args...> _tuple_index(size_t i, const std::tuple<Args...>& tpl) {
if (i == n)
return std::get<n>(tpl);
else if (n == sizeof...(Args) - 1)
throw std::out_of_range("越界.");
else
return _tuple_index<(n < sizeof...(Args)-1 ? n+1 : 0)>(i, tpl);
}
template <typename... Args>
std::variant<Args...> tuple_index(size_t i, const std::tuple<Args...>& tpl) {
return _tuple_index<0>(i, tpl);
}
(3).結構化繫結(Structured bindings)
結構化繫結提供了類似其他語言中提供的多返回值的功能。到目前為止,我們可以通過 std::tuple 來構造一個元組,囊括多個返回值。但缺陷是顯而易見的,我們沒有一種簡單的方法直接從元組中拿到並定義元組中的元素,儘管我們可以使用 std::tie 對元組進行拆包,但我們依然必須非常清楚這個元組包含多少個物件,各個物件是什麼型別。C++17 給出的結構化繫結可以讓我們寫出這樣的程式碼:
std::tuple<int,double,std::string> f() {
return std::make_tuple(1,2.3,"456");
}
auto [x,y,z] = f(); // x,y,z 分別被推導為int,double,std::string
(4).變數宣告的強化
變數的宣告在雖然能夠位於任何位置,甚至於 for 語句內能夠宣告一個臨時變數 int,但始終沒有辦法在 if 和 switch語句中宣告一個臨時的變數。例如:
auto p = map_container.try_emplace(key, value);
if(!p.second) {
//...
} else {
//...
}
C++17 消除了這一限制,使得我們可以:
if (auto p = m.try_emplace(key, value); !p.second) {
//...
} else {
//...
}
C++17未入選特性有:
(1).Concepts
C++ 組委會在討論投票最終確定 C++17 有很多提案,諸如 Concepts/Ranges/Module 等等,其中最受關注的就是 Concepts,可惜這一提案最終被拒,作為技術規範(Technical Specifications, TS) 將其釋出。
Concepts 是對 C++ 模板程式設計的進一步增強擴充套件。簡單來說,Concepts 是一種編譯期的特性,它能夠讓編譯器在編譯期時對模板引數進行判斷,從而大幅度增強我們在 C++ 中模板程式設計的體驗。使用模板進行程式設計時候我們經常會遇到各種令人髮指的錯誤,這是因為到目前為止我們始終不能夠對模板引數進行檢查與限制,例如下面簡單的兩行程式碼會造成大量的幾乎不可讀的編譯錯誤:
#include <list>
#include <algorithm>
int main() {
std::list<int> l = {1, 2, 3};
std::sort(l.begin(), l.end());
return 0;
}
而這段程式碼出現錯誤的根本原因在於,std::sort 對排序容器必須提供隨機迭代器,否則就不能使用,而我們知道 std::list 是不支援隨機訪問的。用 Concepts 的話來說就是:std::list中的迭代器不滿足std::sort中隨機迭代器這個 Concepts(概念) 的 requirements(要求)。有了 Concepts,我們就可以這樣:
template <typename T>
requires Sortable<T> // Sortable 是一個 concept
void sort(T& c);
縮寫為:
template<Sortable T> // T 是一個 Sortable 的型別名
void sort(T& c)
甚至於直接將其作為型別來使用:
void sort(Sortable& c); // c 是一個 Sortable 型別的物件
遺憾的是,C++組委會沒有將 Concetps 納入新標準,而是將其作為TS正式釋出(其實早在 C++11 最終定案之前就已經有 Concepts 的呼聲了,但 Concepts TS 是2015年才完整正式釋出),也就是我們現在看到的 Concepts TS。C++組委會拒絕將 Concepts 納入新標準的原因其實很簡單,並不是技術層面上的原因,純粹是覺得它還不夠成熟。Concepts TS 的釋出到最後一次 C++17 的討論會只相隔了不到四個月的時間,Concepts 的(唯一)實現只存在於一個未釋出的 gcc 版本中。而 gcc 中關於 Concepts 的實現就是由撰寫 Concepts TS 的人開發的,雖然它能夠進行相關測試,但還沒有認真討論過這份 TS 會產生哪些不良後果,更何況這份 TS 都沒有被測試過。此外,已知的 Concepts 的一個明顯的作用就是去輔助實現 Ranges TS 等提案,但實際上它們也沒有被選入 C++17,所以可以把 Concepts 繼續延後。
總的來說,類似於 Concepts/Ranges/Modules 這些令人興奮的特性並沒有入選至 C++17,這注定了 C++17 某種意義上來說相較於 C++11/14 依然只是小幅度更新,但我們有望在 C++2x 中看到這些東西的出現。
C++11/14/17對C++編譯器的支援情況: