Jacek Galovitsによる STL C ++ 17に関する興味深い本の翻訳を終えたところです。

本日、O'ReillyのWebサイトにあるJulian Templemanの記事の翻訳に、新しいC ++標準の標準ライブラリの機能に関する小さな発表を添えてご紹介します。
明けましておめでとうございます!
C ++ 17はメジャーな新しいリリースであり、 100以上の新機能と重要な変更が加えられています。 大きな変更について話すと、新しいバージョンでは、C ++ 11で受け取った右辺値リンクと同等の重要性はありませんが、構造化バインディングや新しいコンテナタイプなど、多くの変更と追加があります。 さらに、C ++言語全体の一貫性を高めるために多くの作業が行われ、開発者は不要な不要な動作を削除しようとしました -たとえば、trigraphsおよび
std::auto_ptr
サポート。
この記事では、C ++ 17の最も重要な2つの革新について説明します。これがないと、開発者は最新のC ++コードを作成する際に、これなしではできません。 構造化型を操作する便利な新しい方法を提供する構造化バインディング、および標準ライブラリに追加されたいくつかの新しい型とコンテナについて説明します。
複数の割り当ての構造化マッピング
構造化バインディングは、まったく新しい現象であると同時に非常に便利です。 これらは、構造型(タプル、配列、構造など)から複数の割り当てを提供します。たとえば、単一の割り当てステートメントで、構造のすべてのメンバーを個別の変数に割り当てます。 したがって、コードはよりコンパクトで理解しやすくなります。
構造バインディングを使用したコードサンプルは、C ++ 17の機能をアクティブにする
-std=c++1z
フラグを指定したclang ++バージョン4コンパイラーを使用してLinux上で実行されます。
C ++ 11では、両方が固定長のコレクションであるという点で配列に似ているタプルが登場しましたが、さまざまなタイプの混合が含まれる場合があります。 タプルを使用すると、次のように関数から複数の値を返すことができます。
#include <tuple> auto get() { return std::make_tuple("fred", 42); }
この単純なコードは2つの要素を持つタプルを返します。C++ 14標準以降では、この関数の自動戻り型を使用できます。これにより、この関数の宣言が他の場合よりもずっときれいになります。 関数の呼び出しは簡単ですが、タプルから値を取得するのはかなり面倒で非論理的に見える可能性があり、
std::get
が必要になる場合があり
std::get
。
auto t = get(); std::cout << std::get<0>(t) << std::endl;
std::tie
を使用して、タプルのメンバーを最初に宣言する必要がある変数にバインドすることもできます。
std::string name; int age; std::tie(name, age) = get();
ただし、C ++ 17で構造化バインディングを使用する場合、タプルメンバーを名前付き変数に直接バインドできます。その後、
std::get
不要になります。または、変数を最初に宣言する必要があります。
auto [name, age] = get(); std::cout << name << " is " << age << std::endl;
この方法で作業することにより、タプルのメンバーへの参照を取得することもできますが、これは
std::tie
を使用する場合には不可能でした。 ここで、タプルのメンバーへの参照を取得し、そのうちの1つの値を変更すると、タプル全体の値が変更されます。
auto t2 = std::make_tuple(10, 20); auto& [first, second] = t2; first += 1; std::cout << "value is now " << std::get<0>(t2) << std::endl;
出力には、
t2
の値が10から11に変更されたことが示されます。
配列と構造の構造化バインディング
タプルの場合は最も明白ですが、構造化バインディングは配列や構造体でも使用できます。次に例を示します。
struct Person { std::string name; uint32_t age; std::string city; }; Person p1{"bill", 60, "New York"}; auto [name, age, city] = p1; std::cout << name << "(" << age << ") lives in " << city << std::endl;
まったく同じ方法で配列を使用する場合:
std::array<int32_t, 6> arr{10, 11, 12, 13, 14, 15}; auto [i, j, k, l, _dummy1, _dummy2] = arr;
この実装には、いくつかの欠点があります。
まず、この欠点は
std::tie
も関連しています-すべての要素をバインドする必要があります。 したがって、たとえば、配列から最初の4つの要素のみを抽出することはできません。 構造体または配列を部分的に抽出する場合は、配列の例に示すように、不要なメンバーをスタブ変数に置き換えるだけです。
第二に(そして、これはScalaやClojureなどの関数型言語でそのようなアイデアを使用することに慣れているプログラマーを失望させます)、デストラクチャリングは1レベルだけです。 Person構造に
Location
メンバーがあるとします:
struct Location { std::string city; std::string country; }; struct Person { std::string name; uint32_t age; Location loc; };
ネストされた初期化を使用して
Person
と
Location
を構築できます。
Person2 p2{"mike", 50, {"Newcastle", "UK"}};
この場合のバインディングはメンバーへのアクセスに役立つと想定できますが、実際には、このような操作は受け入れられないことがわかります。
auto [n, a, [c1, c2]] = p2; //
最後に、必要なデータが公開されていて静的ではないクラスからのみ、この方法でメンバーを抽出できることに注意してください。 この問題については、構造化バインディングに関する次の記事で詳しく説明します。
新しいライブラリタイプとコンテナ
多くの新しく有用なデータ型もC ++ 17の標準ライブラリに追加されており、それらの一部はBoostで作成されました。
このセクションのコードは、Visual Studio 2017でテストされました。
おそらく最も簡単なタイプは
std::byte
別のバイトを表します。 開発者は伝統的に
char
(符号付きまたは符号なし)を使用してバイトを表現していましたが、現在では文字または整数だけでなく型もあります。 確かに、バイトは整数に変換でき、その逆も可能です。
std::byte
、データウェアハウスと対話するように設計されており、算術演算をサポートしませんが、ビット単位の演算はサポートします。
std ::バリアント
「バリアント」の概念は、Visual Basicを扱った人には馴染みがあるように思えるかもしれません。 バリアントは、特定の時点で代替タイプのいずれかの値を含むタイプセーフなユニオンです(さらに、ここでは参照、配列、または
'void'
できません)。
簡単な例:人の年齢を整数または生年月日の文字列として表すことができるデータがあるとします。 そのような情報は、符号なし整数または文字列を含むバリアントを使用して表すことができます。 変数に整数を割り当てて値を設定し、次のように
std::get
で値を抽出できます。
std::variant<uint32_t, std::string> age; age = 51; auto a = std::get<uint32_t>(age);
この方法で定義されていないメンバーを使用しようとすると、プログラムは例外をスローします。
try { std::cout << std::get<std::string>(age) << std::endl; } catch (std::bad_variant_access &ex) { std::cout << "Doesn't contain a string" << std::endl; }
なぜ通常のユニオンではなく
std::variant
使用するのですか? 主にCとの互換性のためにユニオンが言語に存在し、PODタイプではないオブジェクトでは機能しないためです。 これから、特に、カスタムコピーコンストラクタとデストラクタのコピーを持つメンバをユニオンに入れることはそれほど簡単ではないということです。
std::variant
、そのような制限はありません。
std ::オプション
別のタイプの
std::optional
は驚くほど便利で、実際には多くの関数型言語に存在する機能を提供します。
'optional'
は、値を含む場合と含まない場合があるオブジェクトです。 このオブジェクトは、値を返すことができない場合に関数の戻り値として便利に使用されます。 次に、たとえば、nullポインターの代替として機能します。
optional
を使用すると、追加の利点が得られ
optional
。関数の失敗の可能性が宣言で直接明示的に示され、オプションから値を抽出する必要があるため、誤ってゼロ値を使用する可能性が大幅に減少します。
次の例では、文字列を整数に変換しようとする変換関数を定義しています。
optional
返すこと
optional
、関数はこの可能性を残します:変換できない無効な文字列が渡される可能性があります。 呼び出し元は
value_or
関数を使用して
value_or
から値を取得し、関数が失敗した場合、ゼロに等しいデフォルト値を返します(変換が失敗した場合)。
#include <experimental/optional> using namespace std::experimental; optional<int> convert(const std::string& s) { try { int res = std::stoi(s); return res; } catch(std::exception&) { return {}; } } int v = convert("123").value_or(0); std::cout << v << std::endl; int v1 = convert("abc").value_or(0); std::cout << v1 << std::endl;
std :: any
最後に、
std::any
があります。これは、任意の型の単一の値に型保証されたコンテナーを提供します(コピーするときにコンストラクターがある場合)。 anyに値が含まれているかどうかを確認し、次のように
std::any_cast
でその値を抽出できます。
#include <experimental/any> using namespace std::experimental; std::vector<any> v { 1, 2.2, false, "hi!" }; auto& t = v[1].type(); // std::any? if (t == typeid(double)) std::cout << "We have a double" << "\n"; else std::cout << "We have a problem!" << "\n"; std::cout << any_cast<double>(v[1]) << std::endl;
type()
メンバーを使用して、
any
含まれるものを報告する
type_info
オブジェクトを取得
any
ます。 タイプ間の完全な一致が必要です。そうでない場合、プログラムは例外
std::bad_any_cast
をスローします。
try { std::cout << any_cast<int>(v[1]) << std::endl; } catch(std::bad_any_cast&) { std::cout << "wrong type" << std::endl; }
このタイプのデータはいつ便利になりますか? 簡単な答えは、すべての場合で
void*
ポインターを使用できますが、この場合は型の安全性が保証されます。 たとえば、ベース値の異なる表現が必要になる場合があります。たとえば、整数または文字列として「5」を想像してください。 このようなケースはインタープリター言語では一般的ですが、自動的に変換されないプレゼンテーションが必要な場合に役立ちます。
この記事では、C ++ 17の2つの新製品のみをレビューします。C++の専門家も他のすべての新製品に精通することをお勧めします。
GCC、Clang、およびMSVCを含む最も重要なコンパイラーは、すでにこれらの革新の多くをサポートしています。 これについての詳細はこちら 。
インターネットには、C ++ 17に登場したさまざまな革新を説明する非常に優れた要約記事がありますが、その中でも特にTony van Erdの記事、 StackOverflowの詳細な記事、 Bartekの優れた記事に注目します。