[譯]C++17,標準庫有哪些新變化?
看到一個介紹 C++17 的系列博文(原文),有十來篇的樣子,覺得挺好,看看有時間能不能都簡單翻譯一下,這是第二篇~
C++17 有許多新的標準庫變化,簡單起見,這篇文章只介紹了以下內容:std::string_view,標準模板庫中新新增的並行演算法,新的檔案系統庫,以及3個新的資料型別:std::any, std::optional, 和 std::variant.讓我們來了解一下其中的細節.
首先看看 std::string_view.
std::string_view
std::string_view 代表一個字串的非所有權引用(即不負責管理引用字串的生命週期),他表示的是一個字元序列(可以是 C++ 中的 string 或者 C風格的字串)的"檢視".C++17 中為不同的字元型別提供了四種 string_view :
std::string_view std::basic_string_view<char>
std::wstring_view std::basic_string_view<wchar_t>
std::u16string_view std::basic_string_view<char16_t>
std::u32string_view std::basic_string_view<char32_t>
你也許會有疑問:為什麼我們需要 std::string_view 呢(Google, LLVM 和 Bloomberg 甚至實現了自己的 string_view 版本)? .答案其實很簡單: 因為 std::string_view 可以高效的進行復制!
#include <iostream> #include <string> #include <string_view> int main() { std::string str = " A lot of space"; std::string_view strView = str; strView.remove_prefix(std::min(strView.find_first_not_of(" "), strView.size())); std::cout << "str : " << str << std::endl << "strView : " << strView << std::endl; std::cout << std::endl; char arr[] = { 'A', ' ', 'l', 'o', 't', ' ', 'o', 'f', ' ', 's', 'p', 'a', 'c', 'e', '\0', '\0', '\0' }; std::string_view strView2(arr, sizeof arr); auto trimPos = strView2.find('\0'); if (trimPos != strView2.npos) strView2.remove_suffix(strView2.size() - trimPos); std::cout << "arr : " << arr << ", size=" << sizeof arr << std::endl << "strView2: " << strView2 << ", size=" << strView2.size() << std::endl; return 0; }
示例程式碼應該沒有什麼令人驚訝的地方:第8行程式碼建立了引用 C++ string 的 std::string_view(strView變數), 而第16行程式碼中建立的 std::string_view(strView2變數) 引用的則是字元陣列.在第9行程式碼中,我們通過組合使用 remove_prefix 和 find_first_not_of 方法移除了 strView 的所有前導空格符,同樣在第21行程式碼中, 藉助 remove_suffix 方法, strView2 的所有尾隨"\0"符號也被移除了.
下面介紹的內容你應該更加熟悉.
Parallel algorithm of the Standard Template Library(標準模板庫中的並行演算法)
關於STL中並行演算法的介紹比較簡短: 標準庫中的 69 個演算法會提供序列,並行以及向量並行這3個版本.另外,新標準(C++17)也引入了 8 個(此處有誤,見後面譯註)新演算法.下面的示意圖示明瞭所有相關演算法的名字,其中新引入的演算法標為紅色,非新引入的演算法則為黑色.(譯註:圖中紅色標明的 for_each 並非是新演算法,所以實際C++17新引入的演算法只有7個)
演算法的介紹這麼多了,關於這個話題的進一步細節你可以看看我寫的另外一篇文章.
相比較演算法,檔案系統庫應該屬於全新的內容.
The filesystem library
新的檔案系統庫基於 boost::filesystem,並且檔案系統庫中的一些元件是可選的,這意味著並不是每一個檔案系統庫實現都支援標準定義的所有功能.例如, FAT-32 檔案系統便不支援符號連結.
檔案系統庫基於3個概念: 檔案(file), 檔名(file name) 以及 檔案路徑(path). file 可以是目錄,硬連結,符號連結或者常規檔案.path 則可以是絕對路徑或者相對路徑.
filesystem 提供了強大的讀取及操作檔案的介面,
你可以在cppreference.com上獲取到更多細節,下面的示例程式碼可以給你一些初步印象:
#include <fstream>
#include <iostream>
#include <string>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
std::cout << "Current path: " << fs::current_path() << std::endl;
std::string dir = "sandbox/a/b";
fs::create_directories(dir);
std::ofstream("sandbox/file1.txt");
fs::path symPath = fs::current_path() /= "sandbox";
symPath /= "syma";
fs::create_symlink("a", symPath);
std::cout << "fs::is_directory(dir): " << fs::is_directory(dir) << std::endl;
std::cout << "fs::exists(symPath): " << fs::exists(symPath) << std::endl;
std::cout << "fs::symlink(symPath): " << fs::is_symlink(symPath) << std::endl;
for (auto& p : fs::recursive_directory_iterator("sandbox"))
{
std::cout << p.path() << std::endl;
}
fs::remove_all("sandbox");
return 0;
}
第9行程式碼中的 fs::current_path() 方法可以返回當前工作目錄.你也可以使用
fs::create_directories 方法(程式碼第12行)建立層級目錄. fs::path 過載了 /= 操作符,藉助他我們可以方便的建立符號連結(第17行),你也可以使用檔案庫提供的介面來檢查檔案的各項屬性(19行到21行).23行的 fs::recursive_directory_iterator 功能非常強大,你可以使用他來遞迴的遍歷某個目錄,當然,你也可以使用 fs::remove_all 來刪除某個目錄(第27行).
程式碼的輸出如下:
新加入的資料型別 std::any, std::optional, 和 std::variant 都基於 boost程式庫.
std::any
如果你想建立一個可以包含任意型別元素的容器,那麼你就應該使用std::any,不過確切來說的話,std::any 並不是對任意型別都提供儲存支援,只有可複製的型別才能存放入 std::any.下面列一段簡短的示例程式碼:
#include <iostream>
#include <string>
#include <vector>
#include <any>
struct MyClass {};
int main()
{
std::cout << std::boolalpha;
std::vector<std::any> anyVec { true, 2017, std::string("test"), 3.14, MyClass() };
std::cout << "std::any_cast<bool>anyVec[0]: " << std::any_cast<bool>(anyVec[0]) << std::endl; // true
int myInt = std::any_cast<int>(anyVec[1]);
std::cout << "myInt: " << myInt << std::endl; // 2017
std::cout << std::endl;
std::cout << "anyVec[0].type().name(): " << anyVec[0].type().name() << std::endl; // b
std::cout << "anyVec[1].type().name(): " << anyVec[1].type().name() << std::endl; // i
return 0;
}
示例程式碼的輸出已經在註釋中寫明瞭.程式碼第 12 行建立了一個 std::vectorstd::any,你必須使用 std::any_cast 來獲取其中的元素,如果你向 std::any_cast 傳遞了錯誤的資料型別,那麼就會產生轉型異常(std::bad_any_cast).你可以去cppreferenc.com獲取更多相關細節或者等待我之後的更多文章介紹.
std::any 可以儲存任意型別(譯註:這裡的任意型別指可複製的型別)的資料,而 std::optional 則支援儲存資料或者不儲存資料.
std::optional
std::optional 這裡就不做介紹了,在之前我寫的 Monads in C++ 中就已經介紹了這個單子(指std::optional).(譯註: 單子(Monad) 是函數語言程式設計程式設計的概念,簡單理解的話可以看看這裡)
我們再來看下 std::variant.
std::variant
std::variant 是一個型別安全的聯合體(union).一個 std::variant 例項儲存著其指定型別中某一型別的資料,並且 std::variant 的指定型別不能是引用型別,陣列型別以及 void 型別,不過 std::variant 可以指定重複的資料型別(譬如指定多個int). std::variant 預設會以其第一個指定型別進行初始化,這就要求該型別(第一個指定型別)必須支援預設建構函式,下面是一個基於cppreference.com的程式碼示例:
#include <variant>
#include <string>
int main()
{
std::variant<int, float> v, w;
v = 12; // v contains int
int i = std::get<int>(v);
w = std::get<int>(v);
w = std::get<0>(v); // same effect as the previous line
w = v; // same effect as the previous line
//std::get<double>(v); // error: no double in [int, float]
//std::get<3>(v); // error: valid index values are 0 and 1
try
{
float f = std::get<float>(w); // w contains int, not float: will throw
}
catch (std::bad_variant_access&)
{
}
std::variant<std::string> v2("abc"); // converting constructors work when unambiguous
v2 = "def"; // converting assignment also works when unambiguous
return 0;
}
第6行程式碼中我建立了兩個 std::variants 例項 v 和 w,他們的指定型別為 int 和 float,並且初始值為0(第一個指定型別 int 的預設初始值).第7行程式碼中我將整型12賦值給了v,後面我們可以通過 std::get(v) 來獲取該值.第9行到11行程式碼中,我使用了3種方式將v中的數值賦值給了w. std::variants 的使用自然也有一定的規則限制,你可以使用指定某一型別(第9行程式碼)或者指定某一索引(第10行程式碼)的方式來獲取 std::variants 的數值,但是指定的型別必須是唯一的,指定的索引也必須是有效的.第18行程式碼中我嘗試從 w 中獲取 float 型別資料,但是由於 w 目前包含 int 型別資料,所以會產生 std::bad_variant_access 異常.另外值得一提的是, std::variants 的建構函式以及賦值函式支援型別轉換(要求轉換沒有歧義),這也是第24行及25行程式碼中我可以使用C風格的字串直接初始化(或者賦值) std::variantstd::string 的原因.