C++17 - ライブラリの新機能

ライブラリの新機能多くの。長い話を短くするために。 std::string_view、標準テンプレート ライブラリの並列アルゴリズム、ファイル システム ライブラリ、および 3 つの新しいデータ型 std::any、std::optional、および std::variant を取得します。詳細を見てみましょう。

私たちの旅は std::string_view から始まります。

std::string_view

std::string_view は、文字列への非所有参照です。一連の文字のビューを表します。この一連の文字は、C++ 文字列または C 文字列にすることができます。典型的な方法で、C++17 は基礎となる文字型に対して 4 つの型シノニムを提供します。

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 がすでに文字列ビューを実装していたのはなぜですか?答えは簡単です。 std::string_view をコピーするのは非常に安価です。 std::string_view には、文字シーケンスへのポインターとその長さの 2 つの情報のみが必要です。ご想像のとおり、std::string_view とその 3 つの兄弟は、主に std::string のインターフェイスに従う読み取り操作で構成されています。主に、新しいメソッド remove_prefix と remove_suffix を取得するためです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// string_view.cpp

#include <iostream>
#include <string>
#include <experimental/string_view>

int main(){
 
 std::string str = " A lot of space";
 std::experimental::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::experimental::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;
 
}

このプログラムはあなたを驚かせるものではありません。 10 行目と 18 行目の std::string_view は、C++ 文字列と文字配列の参照を取得します。 11 行目では、すべての先頭の非スペース (strView.find_first_not_of(" ")) が削除され、20 行目ではすべての末尾の "\0" 文字 (strView2.find('\0")) が削除されます。名前空間は実験的ですが、cppreference.com で既にプログラムを実行できます。

さて、もっとおなじみのものに。

標準テンプレート ライブラリの並列アルゴリズム

私の話はかなり短いです。標準テンプレート ライブラリ (STL) の 69 のアルゴリズムは、順次、並列および並列、およびベクトル化されたバージョンで利用できます。さらに、8 つの新しいアルゴリズムを取得します。 69 の新しい亜種 (黒) と 8 (赤) の新しいアルゴリズムをすべて見てください。

それだけでした。標準テンプレート ライブラリの Parallel Algorithm という記事を既に書いています。それどころか、ファイルシステム ライブラリは初めてのはずです。

ファイルシステム ライブラリ

新しいファイルシステム ライブラリは、boost::filesystem に基づいています。一部のコンポーネントはオプションです。つまり、ファイルシステム ライブラリの各実装で std::filesystem のすべての機能が利用できるわけではありません。たとえば、FAT-32 はシンボリック リンクをサポートしていません。

ライブラリは、ファイル、ファイル名、およびパスの 3 つの概念に基づいています。ファイルは、ディレクトリ、ハード リンク、シンボリック リンク、または通常のファイルのいずれかです。パスは絶対パスまたは相対パスにすることができます。

ファイルシステムの読み取りと操作のための強力なインターフェースがあります。詳細については、cppreference.com を使用してください。これが第一印象です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// filesystem.cpp

#include <fstream>
#include <iostream>
#include <string>
#include <experimental/filesystem>
namespace fs = std::experimental::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 << std::endl;
 // fs::remove_all("sandbox");
 
}

11 行目の fs::current_path() は、現在のパスを返します。 std::filesystem でディレクトリ階層 (14 行目) を作成できます。 18行目は少し奇妙に見えます。 /=はパスに対してオーバーロードされています。したがって、19 行目で直接シンボリック リンクを作成できます。ファイルのプロパティを確認できます (21 ~ 23 行目)。 26 行目の recursive_directory_iterator の呼び出しは非常に強力です。これを使用して、ディレクトリを再帰的にトラバースできます。もちろん、オンライン コンパイラでディレクトリ (28 行目) を削除することはできません。

これがプログラムの出力です。

新しいデータ型 std::any、std::optional、std::variant の共通点は何ですか?それらはブーストに基づいています。

std::any

任意の型のコンテナが必要な場合は、 std::any が正しい選択です。任意の型は 100% 正しいわけではありません。 std::any は、その値がコピー可能でなければならないことを要求します。以下に短い例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// any.cpp

#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]); // 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(); // b
 std::cout << "anyVec[1].type().name(): " << anyVec[1].type().name(); // i
 
}

プログラムの出力はソースコードにあります。 14 行目では、std::vector を定義しています。その要素の 1 つを取得するには、std::any_cast を使用する必要があります。間違った型を使用すると、std::bad_any_cast 例外が発生します。詳細については、cppreferenc.com にアクセスするか、私の投稿をお待ちください。

std::any は任意の型の値を持つことができ、std::optional は値を持つことも、値を持たないこともできます。

標準::オプション

かなり短くします。 C++ のモナドの投稿で、モナド std::optional について既に書きました。

boost の 3 番目の新しいデータ型は std::variant です。

std::variant

std::variant タイプセーフ共用体。 std::variant のインスタンスには、その型の 1 つからの値があります。型は、参照、配列、または void であってはなりません。共用体は、1 つのタイプを複数回持つことができます。デフォルトで初期化された std::variant は、最初の型で初期化されます。この場合、最初の型にはデフォルトのコンストラクターが必要です。 cppreference.com に基づいた例を次に示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// variant.cpp

#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{
 std::get<float>(w); // w contains int, not float: will throw
 }
 catch (std::bad_variant_access&) {}
 
 std::variant<std::string> v("abc"); // converting constructors work when unambiguous
 v = "def"; // converting assignment also works when unambiguous

}

8 行目で、バリアント v と w の両方を定義します。どちらも int 値と float 値を持つことができます。それらの値は 0 です。 v は 9 行目で 12 になります。 std::get(v) は値を返します。 11 行目から 13 行目では、バリアント v をバリアント w に割り当てる 3 つの可能性が示されています。ただし、いくつかのルールを覚えておく必要があります。タイプ別 (15 行目) またはインデックス別 (16 行目) にバリアントの値を要求できます。タイプは一意で、インデックスは有効でなければなりません。 19 行目では、バリアント w が int 値を保持しています。したがって、21 行目で std::bad_variant_access 例外が発生します。コンストラクターの呼び出しまたは代入の呼び出しが明確な場合、変換を行うことができます。そのため、23 行目で C 文字列を使用して std::variant を作成したり、バリアントに新しい C 文字列を割り当てたりすることができます (24 行目)。

次は?

私は C++17 標準に固執します。この投稿と前回の投稿でコア言語とライブラリの概要を説明した後、次の投稿で詳細を説明します (校正者 Marc Bertola ).