C++17:改善された連想コンテナと均一なコンテナ アクセス

C++11 には 8 つの連想コンテナーがあります。 C++17 では、新しい要素をより快適に挿入したり、既存の連想コンテナーをマージしたり、要素が類似している場合はあるコンテナーから別のコンテナーに要素を移動したりできます。しかし、それだけではありません。連想コンテナとシーケンシャル コンテナへのアクセスが統合されました。

詳細に入る前に、まず次の質問に答えさせてください。類似した連想コンテナとはどういう意味ですか? 8 つの連想コンテナがあります。

同様に、それらの要素が同じ構造と同じデータ型を持っていることを意味します。 std::set と std::multiset、std::unordered_set と std::unordered_multiset、std::map と std::multimap、std::unordered_map と std::unordered_multimap の要素は同じ構造を持ちます。

もちろん、これは 8 つの連想コンテナの概要にすぎません。それには2つの理由があります。まず、改善されたインターフェースについて書きたいと思います。 2 番目に、詳細については、私の以前の投稿であるハッシュ テーブルを参照してください。

まったく新しいものへ。

連想コンテナの改善されたインターフェース

徹底的な例を挙げて、改善されたインターフェースをお見せしましょう。

// accociativeContainers.cpp

#include <iostream>
#include <map>
#include <string>
#include <utility>
 
using namespace std::literals; // 1

template <typename Cont>
void printContainer(const Cont& cont, const std::string& mess){ // 2
 std::cout << mess;
 for (const auto& pa: cont){
 std::cout << "(" << pa.first << ": " << pa.second << ") ";
 }
 std::cout << std::endl;
}

int main(){
 
 std::map<int, std::string> ordMap{{1, "a"s}, {2, "b"}}; // 3
 ordMap.try_emplace(3, 3, 'C');
 ordMap.try_emplace(3, 3, 'c');
 
 printContainer(ordMap, "try_emplace: ");
 
 std::cout << std::endl;
 
 std::map<int, std::string> ordMap2{{3, std::string(3, 'C')}, // 4
 {4, std::string(3, 'D')}};
 ordMap2.insert_or_assign(5, std::string(3, 'e'));
 ordMap2.insert_or_assign(5, std::string(3, 'E'));
 
 printContainer(ordMap2, "insert_or_assign: "); // 5
 
 std::cout << std::endl;
 
 ordMap.merge(ordMap2); // 6
 
 std::cout<< "ordMap.merge(ordMap2)" << std::endl;
 
 printContainer(ordMap, " ordMap: ");
 printContainer(ordMap2, " ordMap2: ");
 
 std::cout << std::endl; 
 
 std::cout << "extract and insert: " << std::endl;
 
 std::multimap<int, std::string> multiMap{{2017, std::string(3, 'F')}}; 
 
 auto nodeHandle = multiMap.extract(2017); // 7
 nodeHandle.key() = 6; 
 ordMap.insert(std::move(nodeHandle)); 

 printContainer(ordMap, " ordMap: ");
 printContainer(multiMap, " multiMap: ");
 
}

ほとんどの場合、std::map が連想コンテナーの最初の選択肢であるため、この例では std::map を使用しています。連想コンテナーが大きく、パフォーマンスが重要な場合は、std::unordered_map について考えてください。投稿連想コンテナ - 単純なパフォーマンス比較では、いくつかのパフォーマンス数値を示しています。

作業を楽にするために、関数テンプレート printContainer (2) を作成して、連想コンテナーを短いメッセージと共に表示しました。同じ引数が using namespace std::literals 式 (1) にも当てはまります。これで、C++ 文字列に新しい組み込みリテラルを使用できるようになりました。 (3) のキーと値のペア {1, "a"s} で使用されていることがわかります。 "a"s は、C++14 以降で使用できる C++ 文字列リテラルです。 C++ 文字列リテラルを取得するには、文字 s を C 文字列リテラル "a" に追加するだけです。

それでは、プログラムを詳しく説明します。よりよく理解するには、出力を見てください。

これらは、新しい要素を連想コンテナーに追加するための 2 つの新しい方法です:try_emplace と insert_or_assign。 ordMap.try_emplace(3, 3, 'C') (3) 新しい要素を ordMap に追加しようとします。最初の 3 は要素のキーで、次の 3 と 'C' は値のコンストラクタに直接行きます。この場合は std::string です。トライといいます。したがって、キーが既に std::map にある場合、何も起こりません。 ordMap2.insert_or_assign(5, std::string(3, 'e')) (4) は異なる動作をします。最初の呼び出し (4) はキーと値のペア 5、std::string("eee") を挿入し、2 番目の呼び出しは std::string("EEE") をキー 5 に割り当てます。

C++17 では、連想コンテナをマージできます (6)。 ordMap.merge(ordMap2) は、連想コンテナ ordMap2 を ordMap にマージします。正式には、このプロセスは「スプライス」と呼ばれます。これは、キーと値のペアで構成される各ノードが ordMap2 から抽出され、キーが ordMap で使用できない場合に ordMap に挿入されることを意味します。キーがすでに ordMap にある場合、何も起こりません。関連するコピーまたは移動操作はありません。転送されたノードへのすべてのポインタと参照は有効なままです。同様のコンテナ間でノードをマージできます。連想コンテナは、同じ構造と同じデータ型を持つ必要があります。

抽出と挿入が続きます (7)。すでに述べたように、各連想コンテナには新しいサブタイプ、いわゆる node_type があります。あるコンテナを別のコンテナにマージすることで暗黙的に使用しました(6)。 node_type を使用して、キーと値のペアのキーを変更することもできます。こちらをご覧ください。 auto nodeHandle multiMap.extract(2017) は、キー 2017 を持つノードを std::multimap から抽出します。次の行で、キーを 6 に変更します:nodeHandle.key() =6 を ordMap に挿入します。ノードを移動する必要があります。コピーはできません。

もちろん、ノードを同じ連想コンテナーに挿入することもできます (A)。そこからノードを抽出するか、キーを変更せずにノードを ordMap に挿入します (B)。ノード (C) の値を変更することもできます。

auto nodeHandle = multiMap.extract(2017); // A 
nodeHandle.key() = 6;
multiMap.insert(std::move(nodeHandle));
 

auto nodeHandle = multiMap.extract(2017); // B 
ordMap.insert(std::move(nodeHandle)); 


auto nodeHandle = multiMap.extract(2017); // C
nodeHandle.key() = 6;
ordMap.insert(std::move(nodeHandle)); 
ordMap[6] = std::string("ZZZ");

std::map、std::unordered_map、std::multimap、または std::unordered_multimap などの値を持つ連想コンテナー (A) からノードを抽出すると、nodeHandleMap を呼び出すことができるノード nodeHandleMap が得られます。 。鍵()。ノードの値を変更する nodeHandleMap.value() メソッドはありません。不思議なことに、ノード nodeHandleSet を std::set またはその 3 つの兄弟の 1 つから抽出すると、nodeHandleSet.value() を呼び出してキーを変更できます。

C++17 には、コンテナーにアクセスするための 3 つの新しいグローバル関数が用意されています。

均一なコンテナ アクセス

3 つの新しい関数の名前は、std::size、std::empty、および std::data です。

  • std::size:STL コンテナ、C++ 文字列、または C 配列のサイズを返します。
  • std::empty:指定された STL コンテナー、C++ 文字列、または C 配列が空かどうかを返します。
  • std::data:コンテナの要素を含むメモリ ブロックへのポインタを返します。コンテナーにはメソッド データが必要です。これは、std::vector、std::string、および std::array に当てはまります。

次は?

私は C++17 について約 10 の記事を書きました。カテゴリ C++17 です。したがって、私は終わりました。この 2 年間、マルチスレッドについて多くの記事を書いてきました。これらの投稿は、理論、実践、C++17 および C++20 との同時実行性、およびメモリ モデルに関するものでした。ご想像のとおり、以前の投稿を締めくくるために、いくつかの新しい投稿を考えています。したがって、既存のマルチスレッド化と今後の C++ 標準の並行性について次の投稿を書く予定です。最初に、いくつかの用語を定義する必要があります。そこで、データ競合と競合状態について書きます。ドイツ語では、2 つの異なる現象に対して同じ用語「kritischer Wettlauf」を使用します。それは非常に悪いことです。並行処理では、簡潔な用語が鍵となるためです。