「C++11/14 高度なプログラミング: Boost ライブラリの探索」メモ
マルチインデックスコンテナーは、名前が示すように、要素にアクセスするための複数の方法を提供します。
場合によっては、同じ要素セットに対して異なるアクセス順序基準を実装することが必要な場合があります。たとえば、リスト内の要素を順番に、サイズを並べ替えて検索したり、要素の特定の属性。ポインタ コンテナと侵入コンテナはある程度解決できます。ポインタ コンテナはビュー アロケータを使用して別のコンテナのビューを確立でき、それによって元のコンテナを変更せずに新しいアクセス方法が提供されます。侵入コンテナは要素の構造を直接変更します。 . 、フックはいくつでも追加でき、各フックはアクセス インターフェイスに対応します。
ただし、制限は次のとおりです。ポインタ コンテナは要素が排他的である必要があり、場合によってはクローン作成の概念を満たす必要があり、侵入コンテナは要素の定義を変更する必要がありますが、これは多くの場合許可されません。
boost.multi_index ライブラリは、コンテナ内の異なるインデックスに格納されている同じ要素セットにアクセスできるマルチインデックス コンテナを提供することで、この種の問題を解決します。
入門例
簡単な例
#include <boost/multi_index_container.hpp> //多索引容器头文件
#include <boost/multi_index/ordered_index.hpp> //有序索引
using namespace boost::multi_index;
int main()
{
multi_index_container<int> mic; //一个多索引容器,缺省使用有序索引
assert(mic.empty());
mic.insert(1);
using namespace boost::assign;
insert(mic)(2), 7, 6, 8;
assert(mic.size() == 5);
assert(mic.count(2) == 1);
assert(mic.find(10) == mic.end());
for (int i : mic)
{
std::cout << i << ','; //顺序输出1,2,6,7,8
}
return 0;
}
複雑な例
#include <boost/multi_index_container.hpp> //多索引容器头文件
#include <boost/multi_index/ordered_index.hpp> //有序索引
#include <boost/multi_index/hashed_index.hpp> //散列(无序)索引
#include <boost/multi_index/key_extractors.hpp> //键提取器
using namespace boost::multi_index;
int main()
{
typedef multi_index_container<int, //多索引容器,容器类型int
indexed_by< //使用index_by元函数定义多个索引
ordered_unique<identity<int>>, //第一个是有序单键索引
hashed_unique<identity<int>> //第二个是散列(无序)单键索引
>
> mic_t;
mic_t mic = { 2,1,7,6,8 }; //多索引容器使用{...}初始化
assert(mic.size() == 5); //默认第一个索引的接口
assert(mic.count(2) == 1);
assert(mic.find(10) == mic.end());
//使用模板成员函数get()获取第二个索引,编号从0算起
auto& hash_index = mic.get<1>();
assert(hash_index.size() == 5); //第二个索引的
assert(hash_index.count(2) == 1);
assert(hash_index.find(10) == hash_index.end());
BOOST_FOREACH(int i,hash_index) //同样可以使用foreach算法
{
std::cout << i << ','; //无序输出
}
return 0;
}
インデックスはメタデータの一部であり、インデックスの生成に使用されるキー タイプを取得するには、キー エクストラクターと呼ばれる関数オブジェクトを使用する必要があります。この例では、要素自体がキーとして直接使用されるため、ID が使用されます。
インデックスを明示的に指定しない場合、コンテナは最初のインデックスを使用します。最初のインデックス以外のインデックスを取得したい場合は、テンプレート メンバー関数get<N>() を使用する必要があります。特定のインデックス タイプ: mic_t::nth_index::type&、通常は auto/decltype を直接使用して宣言を簡素化できます。
auto& hash_index = mic.get<1>(); //correct
auto hash_index = mic.get<1>(); //error,不能声明索引实例变量
より複雑な例
#include <boost/multi_index_container.hpp> //多索引容器头文件
#include <boost/multi_index/ordered_index.hpp> //有序索引
#include <boost/multi_index/hashed_index.hpp> //散列(无序)索引
#include <boost/multi_index/sequenced_index.hpp>//序列索引
#include <boost/multi_index/key_extractors.hpp> //键提取器
using namespace boost::multi_index;
class person :
boost::less_than_comparable<person> //使用operators库实现全序比较
{
public:
int m_id;
std::string m_fname, m_lname;
person(int id, const std::string& f, const std::string& l) :
m_id(id), m_fname(f), m_lname(l) {}
const std::string& first_name() const { return m_fname; }
std::string& last_name() { return m_lname; }
friend bool operator<(const person& l,const person& r)
{
return l.m_id < r.m_id;
}
friend bool operator==(const person& l,const person& r)
{
return l.m_id == r.m_id;
}
friend std::size_t hash_value(const person& p)
{
size_t seed = 2016;
hash_combine(seed, p.m_fname);
hash_combine(seed, p.m_lname);
return seed;
}
};
int main()
{
typedef multi_index_container<person, //多索引容器,容器类型int
indexed_by< //使用index_by元函数定义多个索引
sequenced<>, //第一个是序列索引
ordered_unique<identity<person>>, //第二个是有序单键索引
ordered_non_unique< //第三个是有序多键索引
member<person,std::string,&person::m_fname>>, //使用成员变量
hashed_unique<identity<person>> //第四个是散列(无序)单键索引
>
> mic_t;
mic_t mic; //声明多索引容器
using namespace boost::assign;
push_front(mic) //第一个索引是序列索引,所有可以用push_front
(person(2, "agent", "smith")) //插入四个元素
(person(20, "lee", "someone")) //顺序无关紧要
(person(1, "anderson", "neo")) //id不可重复
(person(10, "lee", "bruce")); //m_fname可重复
auto& index0 = mic.get<0>();
auto& index1 = mic.get<1>();
auto& index2 = mic.get<2>();
auto& index3 = mic.get<3>();
//使用索引0,顺序输出元素,没有排序
for (const person& p : index0)
{
std::cout << p.m_id << p.m_fname << ",";
}
//索引1,获取id最小的元素
assert(index1.begin()->m_id == 1);
//索引2,允许重复键的元素
assert(index2.count("lee") == 2);
//使用assign::insert在索引2上插入重复键的元素
insert(index2)(person(30, "lee", "test"));
assert(index3.size() == 5);
//插入id重复的元素因索引1的限制而不能成功
assert(!index2.insert(person(2, "lee", "test2")).second);
return 0;
}
この例では、コンテナの最初のインデックスは順序付けされており、要素は並べ替えられないため、通常はテンプレート パラメーターは必要ありません。2 番目のインデックスは、順序付けされた単一キー インデックスである Ordered_unique であり、要素に Operator< が必要です。 3 番目のインデックスは requested_non_unique で、ID とは異なる新しいキー抽出メンバーを使用し、侵入コンテナの member_hook と同様の形式で、要素のメンバー変数をキーとして使用できます。4 番目のインデックスは hash_unique (順序付けされていない単一のインデックス) です。要素が boost.hash を満たす必要があるキー インデックスは、ハッシュ値を計算できます。
multi_index ライブラリの基本概念
1.インデックス
インデックスは multi_index ライブラリの最も重要な概念であり、マルチインデックス コンテナ内の要素にアクセスするためのインターフェイスであり、外部ユーザーが要素に対して読み取り/書き込み/検索操作を実行する基準を決定します。インデックスが異なれば使用インターフェースも異なりますが、インターフェースの定義は標準コンテナを完全に模倣しており、最も一般的に使用されるメンバー関数を備えているため、使用時には標準コンテナと見なすことができます。
通常、インデックスでは要素を直接変更することはできません。これは、インデックス コンテナーが複数のインデックスを保持する可能性があり、相互に制限があり、特定の操作が実行できないためです。
インデックスは特定の型ですが、内部的に使用される型として定義されています。インデックス型の変数を自分で宣言することはできません。インデックスの使用は、インデックス仕様で定義されたマルチインデックス コンテナにアタッチする必要があります。 multi_index_containerのテンプレートパラメータを指定し、テンプレートメンバ関数get<N>()を使用して参照を取得して操作します。
2. インデックスの説明
インデックスの説明は、キー エクストラクターとその他のパラメーターを使用してインデックスを定義する高レベルのメタデータです。内部的には、node_class と Index_class という名前の 2 つのメタ関数があり、コンテナーで使用できるノード タイプとインデックス タイプを返します。
現在、multi_index ライブラリは 4 種類のインデックス記述を提供しています。
- シーケンス インデックス: このタイプのインデックスには、std::list 二重リンク リストのシーケンス アクセス インターフェイスと同様に、シーケンスされたインデックスが 1 つだけあります。
- ランダム アクセス インデックス: このタイプのインデックスには、random_access が 1 つだけあります。これは、std::vector のシーケンス アクセス インターフェイスに似ており、operator[] モードでランダム アクセス機能を提供します。
- 順序付きインデックス: std::set に似た順序付きコレクション アクセス インターフェイスである、ordered_unique および requested_non_unique を含みます。
- ハッシュ (順序なし) インデックス: hashed_unique および hashed_non_unique を含み、std::unowned_set に似た順序なしセット アクセス インターフェイスです。
3.キーエクストラクター
キー エクストラクターは、要素または要素の boost.ref ラッパーからインデックス付け (並べ替え) に使用されるキーを取得する単一引数の関数オブジェクトであり、通常はインデックス仕様の最初のテンプレート引数として使用されます。
multi_index ライブラリには、次の 6 つのキー エクストラクターが用意されています。
- アイデンティティ: 要素自体をキーとして使用します
- member: 要素のパブリック メンバー変数をキーとして使用します
- const_mem_fun:要素のconstメンバ関数の戻り値をキーとして使用
- mem_fun:要素の非constメンバ関数の戻り値をキーとして使用
- global_fun: 要素をキーとして動作するグローバル関数または静的メンバー関数の戻り値を使用します
- 複合キー: 上記のキー抽出子を新しいキーに結合できます。
キー抽出プログラムによって取得されるキーの種類は、インデックスの要件を満たす必要があります。たとえば、順序付きインデックスではキーが比較演算子を定義する必要があり、ハッシュ インデックスではキーがハッシュ値を計算して実行できる必要があります。等価比較。
4. インデックス説明リスト
インデックス記述リストは、マルチインデックス コンテナ multi_index_container の 2 番目のテンプレート パラメータであり、indexed_by 構造体として実装され、マルチインデックス コンテナで使用されるインデックスを定義するために使用されます。
Indexed_by は、テンプレート メタプログラミング ライブラリ mpl のメタデータ シーケンス mpl::vector に基づいています。これは、複数のインデックス記述を収容できるコンテナのタイプです。これは前処理メタプログラミングを使用しており、現在の実装は最大 20 個のメタデータに制限されています。これは、最大 20 個のインデックスをコンテナーで同時に使用できることを意味します。
5.インデックスタグ
get<N>() はシリアル番号を通じてインデックスを取得しますが、これは不便で覚えにくいため、multi_index ライブラリはインデックス タグ (タグ) の概念を提供し、構文タグを使用してインデックスにアクセスできるようにします。
Tag はインデックスタグとして最大 20 種類を同時にサポートする mpl 型のコンテナです. タグの型は任意に定義できます. C++ の組み込み型 int と std::string も使用できます. ただし、マルチインデックスコンテナでは重複したタグは許可されません。
ordered_unique<tag<struct id>,...>
hashed_unique<tag<int,short>,...>
auto& ordered_index = mic.get<id>();
auto& hashed_index = mic.get<int>();
6.複数のインデックスコンテナ
マルチインデックスコンテナのタイプは multi_index_container で、宣言は次のとおりです。
template<
typename Value, //元素类型
typename IndexSpecifierList = //索引说明列表
indexed_by<ordered_unique<identity<Value> > >, //缺省值
typename Allocator = std::allocator<Value> > //内存分配器
class multi_index_container;
クラス テンプレートのパラメータは要素型、ソート基準、およびアロケータ型ですが、ソート基準は非常に複雑です。これは、indexed_by 型のインデックス宣言リストです。インデックス記述リストは、デフォルトで、ordered_unique を提供します。つまり、インデックス記述が存在しない場合は、指定されたマルチインデックスコンテナー デフォルトの動作は std::set と同じで、重複を許可しない順序付けられたコレクションです。
キーエクストラクター
1.定義
キーエクストラクターは関数オブジェクトであり、その基本形式は次のとおりです。
struct some_key_extractor
{
typedef T result_type; //返回类型
T& operator()(T& x)const; //操作原始类型
T& operator()(const reference_wrapper<T>& x)const; //操作引用包装类型
template<typename ChainedPtr>
Type& operator()(const ChainedPtr& x)const; //操作链式指针类型
};
キー抽出プログラムの Operator() は、型自体 (T&) だけでなく、boost.ref ライブラリによってラップされたオブジェクト (reference_wrapper<T>&) も操作でき、「チェーン ポインター」オブジェクトもサポートします。
いわゆる「チェーン ポインタ」とは、ポインタ オブジェクトに似た「チェーン ポインタ」の一連の組み合わせを指します。これには、生のポインタ、スマート ポインタ、イテレータ、および逆参照操作を実行できる演算子*、T*、T* を持つその他の型が含まれます。 *、shared_ptr<T*> はすべてチェーン ポインター型であり、継続的かつ再帰的に逆参照して、非ポインター型 T& または Reference_wrapper<T>& を取得できます。この機能により、ポインターの操作が容易になり、複数のインデックス付きコンテナーでポインター タイプの要素を簡単に保持できるようになります。
multi_index ライブラリ内のすべての定義済みキー エクストラクターは、ヘッダー ファイル <boost/multi_index/key_extractors.hpp> にあります。コンパイル時間を短縮したい場合は、必要なヘッダー ファイルを必要に応じて含めることができます。
2.アイデンティティ
Identity は最も単純なキー抽出子です。抽出アクションを実行せずに、要素自体をキーとして直接使用します。標準コンテナのキー型と同等です。要素型が const でない限り、書き込み可能なキー抽出子です。 。
ID はヘッダー ファイル <boost/multi_index/identity.hpp> にあり、クラスの概要は次のとおりです。
template<class Type>
struct identity:
mpl::if_c<
is_const<Type>::value,
detail::const_identity_base<Type>,
detail::non_const_identity_base<Type>
>::type
{};
Type が const によって変更されているかどうかに応じて、メタ関数 if_c を使用して、それぞれ const_identity_base と non_const_identity_base に渡します。
const_identity_base のメインコードは次のとおりです。
template<typename Type>
struct const_identity_base
{
typedef Type result_type; //返回类型定义
//操作元素类型本身
Type& operator()(Type& x)const
{ return x; }
//操作元素类型的reference_wrapper包装
Type& operator()(const reference_wrapper<Type>& x)const
{ return x.get(); }
//操作链式指针
template<typename ChainedPtr>
typename disable_if<
is_convertible<const ChainedPtr&,Type&>,Type&>::type
operator()(const ChainedPtr& x)const
{ return operator()(*x); }
};
最初の 2 つの Operator() は変数自体を直接返し、最後のオーバーロードされたフォームはチェーン ポインターを処理し、逆参照された Operator() を再帰的に生成するために使用されます。これにより、ランタイムは最終的な Type& 型が取得されるまで継続的にそれを呼び出すことができます。
Identity は実際には通常の関数オブジェクトです。次に例を示します。
assert((is_same<string,identity<string>::result_type>::value));
assert(identity<int>()(10) == 10);
assert(identity<string>()("abc") == "abc");
int* p = new int(100); //指针
int** pp = &p; //指针的指针(链式指针)
assert(identity<int>()(pp) == 100); //从链式指针中获取键
3.メンバー
member は、型内のパブリック メンバー変数をキーとして抽出できる、非標準関数オブジェクト select1st の関数に似ています。インターフェースの実装は基本的にidentityと同じで、メタ関数if_cも利用しますが、型Typeがconstで変更されているかどうかに応じて、それぞれconst_member_baseとnon_const_member_baseに引き渡されて処理されます。 const ではない場合、それは書き込み可能なキー抽出プログラムです。
member には 3 つのテンプレート パラメータがあり、侵入型コンテナの member_hook オプションと形式が非常によく似ており、抽出する Class 型、キー型 Type (つまり、その型のメンバ変数型)、およびメンバ変数を指定します。ポインター PtrToMember、例:
typedef pair<int,string> pair_t;
pair_t p(1,"one");
assert((member<pair_t,int,&pair_t::first>()(p) == 1));
assert((member<pair_t,string,&pair_t::second>()(p) == "one"));
person per(1,"anderson","neo");
assert((member<person,int,&person::m_id>()(per) == 1));
C++ 標準のサポートが不十分な一部のコンパイラでは、メンバーを使用できない場合があります。Multi_index は、メンバー ポインタの代わりにオフセットを使用する同等の代替手段、member_offset を提供します。最大限の互換性と使いやすさを得るために、multi_index はマクロ BOOST_MULTI_INDEX_MEMBER を定義します。メンバ変数ポインタの宣言を手動で記述する必要はなく、コンパイラの機能に応じて member または member_offset が自動的に選択されます。
4.const_mem_fun
const_mem_fun は、その型の const メンバー関数の戻り値をキーとして使用します。これは関数オブジェクト mem_fn に似ていますが、2 つの制限があります。const メンバー関数のみを呼び出すことができること、およびこのメンバー関数はパラメーターなしで呼び出す必要があることです。 。
const_mem_fun は、ヘッダー ファイル <boost/multi_index/mem_fun.hpp> にある読み取り専用のキー エクストラクターです。クラスの概要は次のとおりです。
template<class Class,typename Type,
Type (Class::*PtrToMemberFunction)()const>
struct const_mem_fun
{
typedef typename remove_reference<Type>::type result_type;
Type operator() (const Class& x)const
{
return (x.*PtrToMemberFunction)(); //调用无参const成员函数
}
... //其他operator()定义
};
そのテンプレート パラメーターは member に似ていますが、最後のパラメーターはメンバー関数ポインターであり、Class パラメーターと Type パラメーターはメンバー関数ポインターと正確に一致する必要があります。次に例を示します。
string str("abc");
typedef const_mem_fun<string,size_t,&string::size > cmf_t;
assert(cmf_t()(str) == 3);
person per(1,"anderson","neo");
typedef const_mem_fun<person,const string& &person::first_name> cmf_t2;
assert(cmf_t2()(per) == "anderson");
//下面两行会因为类型错误无法编译
typedef const_mem_fun<const person,const string& &person::first_name> cmf_t2;
typedef const_mem_fun<person,string& &person::first_name> cmf_t2;
const_mem_fun には、コンパイラの違いをマスクするためのマクロ BOOST_MULTI_INDEX_CONST_MEM_FUN もあります。
5.mem_fun
mem_fun は const_mem_fun と似ていますが、要素の非 const メンバー関数の戻り値をキーとして使用する点が異なります。読み取り専用のキー抽出プログラムであり、ヘッダー ファイルの場所は const_mem_fun と同じです。
なお、mem_fun は標準ライブラリの関数オブジェクト std::mem_fun と同名ですので、使用する場合は必ず名前空間 boost::multi_index 制限を追加してください。マクロ BOOST_MULTI_INDEX_MEM_FUN を使用することもできるため、名前空間の問題を考慮する必要はありません。
6.グローバルファン
global_fun は、グローバル関数または静的メンバー関数を使用して、関数の戻り値をキーとして要素を操作します。identity と同様に、const 関数または非 const 関数をサポートします。
global_fun は、ヘッダー ファイル <boost/multi_index/global_fun.hpp> にある読み取り専用のキー エクストラクターです。クラスの概要は次のとおりです。
template<class Value,typename Type,Type (*PtrToFunction)(Value)>
struct global_fun:
mpl::if_c<...>::type
{};
使用法は const_mem_fun および mem_fun と似ていますが、最後のテンプレート パラメーターが Value 型のパラメーターを持つ関数ポインターである必要がある点が異なります。
//定义一个person类为参数类型的全局函数
string nameof(const person& p)
{
return p.m_fname + " " + p.m_lname;
}
//使用global_fun
person per(1,"anderson","neo");
typedef global_fun<const person&,string,&nameof> gf_t;
assert(gf_t()(per) == "anderson neo");
//下面两行类型不匹配无法编译通过
typedef global_fun<person&,string,&nameof> gf_t;
typedef global_fun<const person&,string&,&nameof> gf_t;
7. カスタムキーエクストラクター
キー エクストラクターは基本的に単一パラメーターの関数オブジェクトであるため、キー エクストラクターの定義が満たされている限り、multi_index ライブラリによって事前定義されたキー エクストラクターを使用せずに完全に自分で作成できます。
カスタム キー抽出プログラムは、まず標準関数オブジェクトの要件を満たす必要があり、定義にはキー タイプでもある内部タイプ result_type が含まれています。次に、要素型を操作する Operator() を実装するには、それが const メンバー関数である必要があります。T&、reference_wrapper<T>&、ChainedPtr& のいくつかのオーバーロードは必要に応じて実装できますが、すべてを実装する必要はありません。実装されました。
たとえば、キー エクストラクター person_name を記述して、global_fun<const person&,string,&nameof> と同等の機能を実現できます。
struct person_name
{
typedef string result_type; //返回值类型定义,必需
result_type operator()(const person& p)const //必须为const
{
return p.m_fname + " " + p.m_lname;
}
result_type operator()(person *const p)const //支持容纳原始指针
{
return p->m_fname + " " + p->m_lname;
}
}
シーケンスインデックス
シーケンス インデックスは最も単純な種類のインデックスで、実際には要素に対してインデックス処理を実行せず、要素を順番に格納するだけです。シーケンス インデックスはヘッダー ファイル <boost/multi_index/sequenced_index.hpp> にあります。
シーケンス インデックスのインデックス記述は順序付けされており、そのクラスの概要は次のとおりです。
template <typename TagList = tag<> >
struct sequenced
{
template<typename SuperMeta>
struct index_class
{
typedef detail::sequenced_index<...> type;
};
};
シーケンス インデックスはキーに基づいて並べ替えられないため、sequenced はキー エクストラクターを使用せず、インデックス構文のラベル付けに使用される TagList テンプレート パラメーターのみを使用します。
シーケンスインデックスによって使用されるクラスdetail::sequenced_indexは、std::listと同様の二重リンクリスト操作を提供しますが、一部のインターフェイスは定数であり、要素を自由に変更することはできません。
class sequenced_index
{
public:
typedef some_define value_type;
typedef some_define iterator;
typedef iterator const_iterator;
...
//赋值操作
sequenced_index& operator=(const sequenced_index& x);
void assign(InputIterator first,InputIterator last);
void assign(size_type n,const value_type& value);
//迭代器操作
iterator begin();
iterator end();
iterator iterator_to(const value_type& x);
//元素访问
const_reference front()const;
const_reference back()const;
std::pair<iterator,bool> push_front(const value_type& x);
void pop_front();
std::pair<iterator,bool> push_back(const value_type& x);
void pop_back();
std::pair<iterator,bool> insert(iterator position,const value_type& x);
void insert(iterator position,size_type n,const value_type& x);
iterator erase(iterator position);
iterator erase(iterator first,iterator last);
... //其他remove()、unique()、splice()、sort()操作
... //各种比较操作定义
}
sequence_index のインターフェースは基本的に list と同じですが、主な注意点は次のとおりです。
- インデックスにはパブリック コンストラクターと分析関数 (マルチインデックス コンテナーによって処理される) は提供されませんが、operator= および assign() を使用して値を割り当てることができ、使用法は標準コンテナーと同じです。
- 逆参照イテレータ (begin()、rbegin() など) は const 型を返し、イテレータを使用して要素を変更することはできません。
- 要素にアクセスするfront()およびback()関数はconst参照を返し、要素を変更できません。
- 他のインデックス制約がある可能性があるため、push_front()、push_back()、および insert() はすべて失敗する可能性があるため、戻り値は set::insert() と同じペアになり、2 番目のメンバーは操作が成功したかどうかを示します。
- 侵入型コンテナと同様に、インデックスには iterator_to() 関数が用意されており、コンテナ内の要素の参照から対応するイテレータを取得できます。
使用法:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/assign.hpp>
using namespace boost::multi_index;
using namespace std;
int main()
{
typedef multi_index_container<int,
indexed_by<sequenced<
tag<int,struct int_seq> > >
> mic_t;
using namespace boost::assign;
mic_t mic = (list_of(2),3,5,7,11);
assert(!mic.empty() && mic.size() == 5);
assert(mic.front() == 2);
assert(mic.back() == 11);
assert(mic.push_front(2).second);
assert(mic.push_back(19).second);
auto& seq_index = mic.get<int_seq>(); //使用标签获得索引
seq_index.insert(seq_index.begin(),5,100); //插入5个元素
assert(std::count(seq_index.begin(),mic.end(),100) == 5);
seq_index.unique();
assert(std::count(seq_index.begin(),mic.end(),100) == 1);
//支持容器间的比较操作
mic_t mic1 = (list_of(2),3,5,7,11);
mic_t mic2 = mic1;
assert(mic1 == mic2);
mic2.push_back(3);
assert(mic1<mic2);
// *seq_index.begin() = 9; //编译出错,不能修改元素的值,报const错
auto& x = mic.front();
assert(mic.begin() == mic.iterator_to(x));
}
ランダムアクセスインデックス
ランダム アクセス インデックスもシーケンス インデックスの一種で、これも要素を順番に格納しますが、シーケンス インデックスよりも多くのアクセス インターフェイスを提供します。
ランダム アクセス インデックスは、ヘッダー ファイル <boost/multi_index/random_access_index.cpp> にあります。
そのインデックス仕様は、random_access であり、sequenced と非常によく似ています。また、キー エクストラクターを使用せず、ラベル テンプレート パラメーターのみを持ちます。
ランダム アクセス インデックスに使用されるクラスは、detail::random_access_index です。これは sequenced_index のスーパーセットであり、sequenced_index のすべての関数を備えています。さらに 2 つの演算子 [] と at() があり、任意の位置の要素にランダムにアクセスできます。は同じですが、操作の時間の複雑さは異なります。
random_access_index は使用方法が std::vector に非常に似ていますが、本質的にはチェーン コンテナーであり、std::vector のような連続要素ストレージを提供しません。
使用法:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/assign.hpp>
using namespace boost::multi_index;
using namespace std;
int main()
{
typedef multi_index_container<int,
indexed_by<random_access<> >
> mic_t;
using namespace boost::assign;
mic_t mic1 = (list_of(2),3,5,7,11);
assert(mic1[0] == 2);
assert(mic1.at(2) == 5);
mic1.erase(boost::next(mic1.begin(),2));
assert(mic1[2] == 7);
mic_t mic2;
mic2.splice(mic2.end(),mic1); //使用链表的接合操作
assert(mic1.empty() && mic2.size() == 4);
push_front(mic1)(8),10,20,16;
mic1.sort();
mic2.sort();
mic1.merge(mic2);
//逆序输出元素
for(auto itr = mic1.rbegin();itr!=mic1.rend();++itr)
{
std::cout << *itr << ",";
}
}
random_access_index は要素にランダムにアクセスする機能を提供しますが、そのインターフェイスは定数であるため、イテレータ割り当て操作の使用を必要とする多くの標準アルゴリズム (ソート アルゴリズムや置換アルゴリズムなど) は使用できません。
std::sort(mic1.begin(),mic1.end()); //编译出错
std::random_shuffle(mic1.begin(),mic1.end()); //编译出错
std::replace(mic1.begin(),mic1.end(),2,222); //编译出错
順序付きインデックス
順序付きインデックスは、キー抽出子に基づいて要素を並べ替え、赤と黒のツリー構造を使用して、セットと同様の順序付きセット アクセス インターフェイスを提供します。ヘッダーファイル <boost/multi_index/owned_index.hpp>
にある順序付けされたインデックスのインデックスの説明には、 ordered_unique と requested_non_unique が含まれています。前者では重複キーが許可されず、後者では重複が許可されます。宣言とインターフェイスは両方とも同じです。例として、クラスの概要は次のとおりです。
template<typename Arg1,typename Arg2=mpl::na,typename Arg3=mpl::na>
struct ordered_unique
{
template<typename SuperMeta>
struct index_class
{
typedef detail::ordered_index<...> type;
};
};
3 つのテンプレート パラメーターがあり、テンプレート メタプログラミング技術を使用しているため、少なくとも 1 つのテンプレート パラメーターを指定することで機能します。
- 最初のパラメータはラベルまたはキー抽出子であり、指定する必要があります。
- 最初の引数がラベルの場合、2 番目の引数はキー抽出子でなければなりません
- 最後のパラメータは比較述語オブジェクトです。デフォルトは std::less<typename KeyFromValue::result_type> で、これはキー抽出器の小なり比較です。
順序付きインデックスで使用されるクラスは、detail::owned_index であり、インターフェイスは std::set に似ています。
template<typename KeyFromValue,typename Compare,...>
class ordered_index
{
public:
typedef some_define key_type;
typedef some_define value_type;
typedef some_define iterator;
typedef iterator const_iterator;
...
//赋值操作
ordered_index& operator=(const ordered_index& x);
//迭代器操作
iterator begin();
iterator end();
iterator iterator_to(const value_type& x);
//元素访问
std::pair<iterator,bool> insert(const value_type& x);
void insert(iterator position,const value_type& x);
iterator erase(iterator position);
//有序相关操作
iterator find(const CompatibleKey& x)const;
iterator find(const CompatibleKey& x,const CompatibleCompare& comp)const;
size_type count(const CompatibleKey& x)const;
size_type count(const CompatibleKey& x,const CompatibleCompare& comp)const;
iterator upper_bound(const CompatibleKey& x)const;
iterator upper_bound(const CompatibleKey& x,const CompatibleCompare& comp)const;
std::pair<iterator,iterator> equal_range(const CompatibleKey& x)const;
std::pair<iterator,iterator> equal_range(const CompatibleKey& x,const CompatibleCompare& comp)const;
std::pair<iterator,iterator> range(LowerBounder lower,UpperBounder upper)const;//参数为两个函数对象
};
使用法は std::set とあまり変わりませんが、反復子を使用して要素を変更することはできないことに注意してください。
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/assign.hpp>
#include <boost/foreach.hpp>
#include <string>
using namespace boost::multi_index;
using namespace std;
int main()
{
//单键有序索引容器
typedef multi_index_container<int,
indexed_by<
ordered_unique<identity<int>>
>
> mic_t1;
using namespace boost::assign;
mic_t1 mic1;
insert(mic1)(2),3,5,7,11;
assert(mic1.size() == 5);
assert(!mic1.insert(3).second);
assert(mic1.find(7) != mic1.end());
//多键有序索引容器
typedef multi_index_container<string,
indexed_by<
ordered_non_unique<
BOOST_MULTI_INDEX_CONST_MEM_FUN(string,size_t,size)>
>
> mic_t2;
mic_t2 mic2;
insert(mic2)("111")("22")("333")("4444");//重复元素
assert(mic2.count(3) == 2); //两个重复元素
//使用equal_range()输出重复元素,不能用for
BOOST_FOREACH(auto& str,mic2.equal_range(3))
{
std::cout << str << ",";
}
}
順序付きインデックスには、互換キーの比較と範囲間隔の取得という 2 つのハイテクな用途があり、例として person クラスを取り上げます。
互換キー比較、いわゆる互換キーは、インデックス記述内のキー自体とは異なるタイプを指しますが、その比較効果はキーと同じです。たとえば、person クラスでは、identity<person> によって定義されたキーの型は person ですが、その比較演算子 Operator< は int 型の m_id メンバー変数を使用するため、int はその互換キーであり、int を構築するのは明らかです。 type 人型を構築するよりもコストが大幅に安くなり、当然効率も上がります。
互換キー比較関数を使用するには、int 型と比較する person クラスの述語を定義する必要があります。
struct compare_by_id
{
typedef bool result_type;
bool operator()(const person& p,int id)const
{
return p.m_id < id;
}
bool operator()(int id,const person& p )const
{
return id < p.m_id;
}
};
これにより、以下を使用する場合に比較を実行するために要素全体を使用する必要がなくなります。
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/assign.hpp>
#include <boost/foreach.hpp>
#include <boost/functional/hash.hpp> //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;
class person :
boost::less_than_comparable<person> //使用operators库实现全序比较
{
public:
int m_id;
std::string m_fname, m_lname;
person(int id, const std::string& f, const std::string& l) :
m_id(id), m_fname(f), m_lname(l) {}
const std::string& first_name() const { return m_fname; }
std::string& last_name() { return m_lname; }
friend bool operator<(const person& l,const person& r)
{
return l.m_id < r.m_id;
}
friend bool operator==(const person& l,const person& r)
{
return l.m_id == r.m_id;
}
friend std::size_t hash_value(const person& p)
{
size_t seed = 2016;
boost::hash_combine(seed, p.m_fname);
boost::hash_combine(seed, p.m_lname);
return seed;
}
};
struct compare_by_id
{
typedef bool result_type;
bool operator()(const person& p,int id)const
{
return p.m_id < id;
}
bool operator()(int id,const person& p )const
{
return id < p.m_id;
}
};
int main()
{
//单键有序索引容器
typedef multi_index_container<person,
indexed_by<
ordered_unique<identity<person>>
>
> mic_t;
using namespace boost::assign;
mic_t mic;
insert(mic)
(person(2,"agent","smith"))
(person(20,"lee","someone"))
(person(1,"anderson","neo"))
(person(10,"lee","bruce"));
//构造一个大对象执行比较操作,成本很高
assert(mic.count(person(1,"abc","xby")) == 1);
//使用自定义的兼容键比较谓词
assert(mic.count(1,compare_by_id()) == 1);
assert(mic.find(10,compare_by_id()) != mic.end());
}
範囲間隔を取得するには、テンプレート関数であるメンバー関数 range() が使用されます。パラメータ (LowerBounder および UpperBounder) は、下限と上限を決定するために使用される、パラメータとしてキーを持つ 2 つの述語関数または関数オブジェクトです。これは a < x && x < b に相当します。これは、 lower_bound() と upper_bound() を直接使用するよりも便利で直感的です。
たとえば、人の ID に対して 2 <= p.m_id < 20 の左閉および右開の間隔を定義します。 lower_bound() と upper_bound() の使用法は次のとおりです。
//获得id>=2的下界
mic_t::iterator l = mic.lower_bound(2,compare_by_id());
//获得id>=20的下界,即<20的上界
mic_t::iterator u = mic.lower_bound(20,compare_by_id());
//foreach循环,使用make_pair构造一个迭代器区间输出元素
BOOST_FOREACH(const person& p,std::make_pair(l,u))
{
std::cout << p.m_id << ":" << nameof(p) << endl;
}
この書き方は混乱しやすく、エンドポイントの条件を把握するのが難しくなりますが、順序付けされたインデックスの range() 関数を使用する方がはるかに簡単で、上限と下限の関数オブジェクトを明確に実装できます。
struct lower_bounder //定义p>=2
{
typedef bool result_type;
bool operator()(const person& p)
{ return p.m_id >= 2; }
};
struct upper_bounder //定义p<20
{
typedef bool result_type;
bool operator()(const person& p)
{ return p.m_id < 20; }
};
//调用range()获取这个区间
BOOST_FOREACH(const person& p,mic.range(lower_bounder(),upper_bounder()))
{
std::cout << p.m_id << ":" << nameof(p) << endl;
}
C++11/14 ラムダ式を使用すると、上記の面倒な上限述語と下限述語の記述を簡素化し、匿名関数オブジェクトを生成できます。
BOOST_FOREACH(const person& p,mic.range(
[](const person& p){ return p.m_id >= 2; }, //使用C++11/14的lambda
[](const person& p){ return p.m_id < 20; }))
順序付きインデックスは、特別な述語関数 unbounded も定義します。これは、区間の任意のエンドポイントで使用して、エンドポイントが無制限であることを示します。
mic.range(lower_bounder(),unbounded); //p.m_id >= 2
mic.range(unbounded,upper_bounder()); //p.m_id < 20
mic.range(unbounded,unbounded); //所有元素
ハッシュインデックス
ハッシュ インデックスは、(要素の) キー エクストラクターに基づいてハッシュし、std::unowned_set に似た順序なしセット インターフェイスを提供します。ハッシュ インデックスはヘッダー ファイル <boost/multi_index/hashed_index.hpp> にあります。
ハッシュ インデックスのインデックスの説明には hashed_unique と hashed_non_unique が含まれます。前者は重複キーを許可せず、後者は重複キーを許可します。の宣言とインターフェイスどちらも同じです。例として hashed_unique を取り上げます。
template<typename Arg1,typename Arg2,typename Arg3,typename Arg4>
struct hashed_uniqued
{
template<typename SuperMeta>
struct index_calss
{
typedef detail::hashed_index<...> type;
};
};
これは 4 つのテンプレート パラメーターを提供し、ordered_unique と同じテンプレート メタプログラミング テクノロジを使用しており、少なくとも 1 つのテンプレート パラメーターを提供することで機能します。
- 最初の引数はラベルまたはキー抽出機能を有効にすることができ、これを指定する必要があります
- 最初の引数がラベルの場合、2 番目の引数はキー抽出子でなければなりません
- キー抽出子の後のパラメータはハッシュ関数オブジェクトで、デフォルトは boost::hash<typename KeyFromValue::result_type> です。
- 最後のパラメータは等価比較述語オブジェクトで、デフォルトは std::equal_to<typename KeyFromValue::result_type> です。
ハッシュ インデックスで使用されるクラスは hashed_index です。インターフェイスは std::unowned_set に似ています。クラスの概要は次のとおりです。
template<typename KeyFromValue,typename Hash,typename Pred,...>
class hashed_index
{
public:
typedef some_define key_type;
typedef some_define value_type;
typedef some_define iterator;
typedef iterator const_iterator;
...
//赋值操作
hashed_index& operator=(const hashed_index& x);
//迭代器操作
iterator begin();
iterator end();
iterator iterator_to(const value_type& x);
//元素访问
iterator find(const CompatibleKey& x)const;
iterator find(const CompatibleKey& x,
const CompatibleHash& hash,
const CompatiblePred& eq)const;
size_type count(const CompatibleKey& x)const;
size_type count(const CompatibleKey& x,
const CompatibleHash& hash,
const CompatiblePred& eq)const;
std::pair<iterator,iterator> equal_range(const CompatibleKey& x)const;
std::pair<iterator,iterator> equal_range(const CompatibleKey& x,
const CompatibleHash& hash,
const CompatiblePred& eq)const;
};
hashed_index のインターフェイスは unowned_set のインターフェイスに似ていますが、順序付けされていないため、メンバー関数 lower_bound()、upper_bound()、および range() は提供されません。さらに、 hashed_index には、バケット数、負荷係数など、ハッシュ コンテナーに関連するいくつかの特別なメンバー関数もあります。
使用法ハッシュ インデックスは std::unowned_set のようなものです。
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/assign.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp> //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;
class person :
boost::less_than_comparable<person> //使用operators库实现全序比较
{
public:
int m_id;
std::string m_fname, m_lname;
person(int id, const std::string& f, const std::string& l) :
m_id(id), m_fname(f), m_lname(l) {}
const std::string& first_name() const { return m_fname; }
std::string& last_name() { return m_lname; }
friend bool operator<(const person& l,const person& r)
{
return l.m_id < r.m_id;
}
friend bool operator==(const person& l,const person& r)
{
return l.m_id == r.m_id;
}
friend std::size_t hash_value(const person& p)
{
size_t seed = 2016;
boost::hash_combine(seed, p.m_fname);
boost::hash_combine(seed, p.m_lname);
return seed;
}
};
string nameof(const person& p)
{
return p.m_fname + " " + p.m_lname;
}
int main()
{
typedef multi_index_container<person,
indexed_by<
hashed_unique<identity<person>>
>
> mic_t;
using namespace boost::assign;
mic_t mic;
insert(mic)
(person(2,"agent","smith"))
(person(1,"anderson","neo"))
(person(10,"lee","bruce"));
assert(mic.size() == 3);
assert(mic.find(person(1,"anderson","neo")) != mic.end());
//散列索引同样可以使用兼容键来查找元素,但需要两个函数对象进行散列和相等比较,比有序索引要多做一些工作
//person类散列使用了m_fname和m_lname,相等比较使用了m_id,涉及元素较多,所以使用一个boost::tuple来定义兼容键
typedef boost::tuple<int,string,string> hash_key_t;
//定义散列函数对象
struct hash_func
{
typedef size_t result_type;
size_t operator()(const hash_key_t& k)const
{
size_t seed = 2016;
boost::hash_combine(seed,k.get<1>());
boost::hash_combine(seed,k.get<2>());
return seed;
}
};
//定义相等比较函数对象
struct equal_func
{
typedef bool result_type;
bool operator()(const hash_key_t& k,const person& p)const
{ return k.get<0>() == p.m_id; }
bool operator()(const person& p,const hash_key_t& k)const
{ return k.get<0>() == p.m_id; }
};
assert(mic.count(boost::make_tuple(1,"anderson","neo"),hash_func(),equal_func()) == 1);
assert(mic.find(boost::make_tuple(10,"lee","bruce"),hash_func(),equal_func()) != mic.end());
return 0;
}
要素を変更する
multi_inde ライブラリ内のすべてのインデックスの反復子インターフェイスは定数であり、変更操作により他のインデックスの不整合が生じる可能性があるため、ユーザーが直接変更することはできません。このため、multi_index は別のメカニズムを使用します。すべてのインデックスは 2 つの専用メンバーを提供します。要素を変更するための関数: replace() およびmodify()、順序付けされたインデックスおよびハッシュ インデックスも、キーを変更する特別なメンバー関数modify_key()を提供します。これらの操作により、マルチインデックス コンテナの状態が破壊されるのを防ぐことができます。
要素の置換
メンバー関数 replace() は、有効な反復子の位置にある要素の値を置き換えることができ、他のインデックスは同期的に更新されたままになります。
通常、find() アルゴリズムまたは find() メンバー関数を使用して反復子の位置を取得し、呼び出してbool replace(iterator position,const value_type&x);
要素を置換できます。iterator_to() を使用する場合は注意が必要で、要素の同等のコピーを使用すると無効なイテレータが生成され、replace() を使用すると実行時例外が発生します。
例:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/assign.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp> //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;
class person :
boost::less_than_comparable<person> //使用operators库实现全序比较
{
public:
int m_id;
std::string m_fname, m_lname;
person(int id, const std::string& f, const std::string& l) :
m_id(id), m_fname(f), m_lname(l) {}
const std::string& first_name() const { return m_fname; }
std::string& last_name() { return m_lname; }
friend bool operator<(const person& l,const person& r)
{
return l.m_id < r.m_id;
}
friend bool operator==(const person& l,const person& r)
{
return l.m_id == r.m_id;
}
friend std::size_t hash_value(const person& p)
{
size_t seed = 2016;
boost::hash_combine(seed, p.m_fname);
boost::hash_combine(seed, p.m_lname);
return seed;
}
};
string nameof(const person& p)
{
return p.m_fname + " " + p.m_lname;
}
struct compare_by_id
{
typedef bool result_type;
bool operator()(const person& p,int id)const
{
return p.m_id < id;
}
bool operator()(int id,const person& p )const
{
return id < p.m_id;
}
};
int main()
{
typedef multi_index_container<person,
indexed_by<
ordered_unique<identity<person>>, //单键有序索引
ordered_non_unique< //有序多键索引
member<person,string,&person::m_fname>>,
hashed_unique<identity<person>> //散列单键索引
>
> mic_t;
using namespace boost::assign;
mic_t mic;
insert(mic)
(person(2,"agent","smith"))
(person(20,"lee","someone"))
(person(1,"anderson","neo"))
(person(10,"lee","bruce"));
auto pos = mic.find(20,compare_by_id()); //查找id为20的元素
assert(pos != mic.end());
mic.replace(pos,person(20,"lee","long")); //替换这个元素
assert(pos->m_lname == "long");
mic.replace(pos,person(15,"lee","long")); //修改元素的键
//如果修改后元素与索引约束发生冲突则会导致替换失败,函数返回false
assert(!mic.replace(pos,person(2,"lee","someone"))); //索引0冲突
assert(!mic.replace(pos,person(10,"lee","bruce"))); //索引2冲突
return 0;
}
変更された要素
の replace() メンバー関数は強力な例外安全性を保証しますが、操作中に完全な一時要素オブジェクトを構築する必要があり、操作コストが高くなります。modify() は、要素を変更する別のメソッドです。これは、モディファイアと呼ばれる関数または関数オブジェクトを使用し、要素への参照をパラメータとして受け取り、要素の特定のコンポーネントのみを変更できます。これは軽量の変更要素です。 。
変更ステートメントは次のようになります。
template<typename Modifier>
bool modify(iterator position,Modifier mod);
これには 2 つのパラメータがあり、1 つ目は変更する反復子の位置 (replace() と同じ)、2 つ目は要素の変更操作を実行する修飾子オブジェクトです。
たとえば、person の各メンバー変数を変更したい場合、C++11/14 のラムダ式を使用して、modify() に直接修飾子を記述することができます。
mic.modify(pos,
[](person& p){ p.m_id = 15; });
assert(pos->m_id == 15);
mic.modify(pos,
[](person& p){ p.m_fname = "mike"; });
assert(pos->m_fname == "mike");
mic.modify(pos,
[](person& p){ p.m_lname = "david"; });
assert(pos->m_lname == "david");
modify() は、一時オブジェクトの構築コストを回避し、実行効率が高くなりますが、操作の安全性は保証できません。要素が変更されてインデックスの制約と矛盾する場合、変更は失敗し、要素は削除されます。!
auto pos = mic.find(20,compare_by_id()); //找到id为20的元素
//将id修改为1,与索引0的约束(ordered_unique)发生冲突,修改失败,元素被删除
assert(!mic.modify(pos,[](person& p){ p.m_id = 1; }));
//此时元素已被删除,无法找到
assert(mic.size() == 3);
assert(mic.find(20,compare_by_id()) == mic.end());
この現象を解決するために、modify() はデータベースのロールバック メカニズムに似たオーバーロードされた形式を提供し、ユーザーが「ロールバック」関数または関数オブジェクトを使用して、変更が失敗したときに元の値を復元できるようにします。関数のプロトタイプは次のとおりです。
template<typename Modifier,typename Rollback>
bool modify(iterator position,Modifier mod,Rollback back);
使用方法は次のとおりです。
auto tmp = pos->m_id; //修改前先保存原值
assert(!mic.modify(pos,
[](person& p){ p.m_id = 1;},
[](person& p){ p.m_id = tmp;})); //回滚操作,修改失败回滚恢复原值
assert(mic.size() == 4);
assert(mic.find(20,compare_by_id()) != mic.end());
キーの変更:
順序付きインデックスとハッシュ インデックスには特別な変更関数 modify_key() があり、要素自体ではなくインデックスで使用されるキーを直接変更できます。modify_key() を使用するには、インデックスのキー抽出プログラムが書き込み可能である必要があります。
modify_key の宣言は次のとおりです。
template<typename Modifier>
bool modify_ke(iterator position,Modifier mod);
template<typename Modifier,typename Rollback>
bool modify_key(iterator position,Modifier mod,Rollback back);
modify_key() の修飾子は要素自体の型ではなくキーの型に作用するため、ランバ式を記述するときはキーの型をパラメータとして使用する必要があります。
auto& index = mic.get<1>(); //获取索引
auto pos = index.find("agent"); //查找元素
index.modify_key(pos,
[](string& str){ str = "virus"; });
assert(pos->m_fname == "virus");
//同样,如果modify_key()修改键后导致索引冲突,那么元素也会被删除,使用回滚操作避免
auto tmp = pos->m_fname;
index.modify_key(pos,
[](string& str){ str = "virus"; },
[](string& str){ str = tmp; });
複数のインデックスコンテナ
キーエクストラクターとインデックスは以前に紹介しましたが、このセクションでは真のマルチインデックスコンテナーを紹介します。
マルチインデックスコンテナの前方宣言は上にリストされており、そのクラスの概要は次のとおりです。
template<
typename Value, //元素类型
typename IndexSpecifierList=indexed_by<...>, //索引说明列表
typename Allocator=std::allocator<Value> > //内存分配器
class multi_index_container:
public detail::multi_index_base_type<...>::type
{
public:
//构造函数、赋值操作
multi_index_container(InputIterator first,InputIterator last);
multi_index_container(const multi_index_container& x);
multi_index_container& operator=(const multi_index_container& x);
//索引类型定义
template<int N> struct nth_index;
template<typename Tag> struct index;
//获取索引操作
template<int N>
typename nth_index<N>::type& get();
template<typename Tag>
typename index<Tag>::type& get();
//投射操作
template<int N,typename IteratorType>
typename nth_index<N>::type::iterator project(IteratorType it);
template<typename Tag,typename IteratorType>
typename index<Tag>::type::iterator project(IteratorType it);
};
本質的に、multi_index_container はインデックス コンテナであり、インデックスの管理のみを担当し、各インデックスは要素の管理を担当します。
get<N>() は最も重要なメンバー関数です。これには 2 つのオーバーロード形式があります。整数のシリアル番号または型タグを使用してインデックスを取得できます。したがって、インデックスにも nth_index<N> とインデックスの 2 種類があります。 <tag>. , これら 2 つは実際にはメタ関数です。実際のインデックス型を取得するには ::type の形式を使用する必要があります。通常、auto/decltype は単に型宣言の問題を回避するために使用されます。
project<N>() は、複数の異なるインデックス間の変換に使用されるイテレータです。あるインデックスのイテレータを別のインデックスのイテレータに射影でき、2 つのイテレータは同じ要素を指します。
必要でない場合は、マクロ BOOST_MULTI_INDEX_DISABLE_SERIALIZATION を定義してシリアル化コードを無効にし、コンパイルを高速化できます。
8 つのインデックスを持つマルチインデックス コンテナーの使用例:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/global_fun.hpp>
#include <boost/assign.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp> //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;
class person :
boost::less_than_comparable<person> //使用operators库实现全序比较
{
public:
int m_id;
std::string m_fname, m_lname;
person(int id, const std::string& f, const std::string& l) :
m_id(id), m_fname(f), m_lname(l) {}
const std::string& first_name() const { return m_fname; }
std::string& last_name() { return m_lname; }
friend bool operator<(const person& l,const person& r)
{
return l.m_id < r.m_id;
}
friend bool operator==(const person& l,const person& r)
{
return l.m_id == r.m_id;
}
friend std::size_t hash_value(const person& p)
{
size_t seed = 2016;
boost::hash_combine(seed, p.m_fname);
boost::hash_combine(seed, p.m_lname);
return seed;
}
};
string nameof(const person& p)
{
return p.m_fname + " " + p.m_lname;
}
struct person_name
{
typedef string result_type; //返回值类型定义,必需
result_type operator()(const person& p)const //必须为const
{
return p.m_fname + " " + p.m_lname;
}
result_type operator()(person *const p)const //支持容纳原始指针
{
return p->m_fname + " " + p->m_lname;
}
};
struct compare_by_id
{
typedef bool result_type;
bool operator()(const person& p,int id)const
{
return p.m_id < id;
}
bool operator()(int id,const person& p )const
{
return id < p.m_id;
}
};
typedef sequenced<tag<int,struct seq_idex> > idx_sf0; //序列索引 类似std::list一样双向链表
typedef random_access<tag<struct rnd_idx,string> > idx_sf1; //随机访问索引 类似std::vector一样随机访问序列
typedef ordered_unique<identity<person>> idx_sf2; //有序单键索引 基于m_id从小到大排序的不允许重复有序集合
typedef ordered_non_unique< //有序多键索引 基于m_fname从小到大排序的允许重复有序集合
BOOST_MULTI_INDEX_MEMBER(person,string,m_fname)> idx_sf3;
typedef ordered_unique< //有序单键索引 基于m_fname从大到小排序的不允许重复有序集合
BOOST_MULTI_INDEX_CONST_MEM_FUN(
person,const string&,first_name),
std::greater<const string> > idx_sf4; //使用大于比较排序
typedef hashed_unique< //散列单键索引 基于m_lname的不允许重复的无序集合
BOOST_MULTI_INDEX_MEMBER(person,string,m_lname)> idx_sf5;
typedef hashed_non_unique< //散列多键索引 基于m_fname+m_lname的允许重复的无序集合
global_fun<const person&,string,&nameof>> idx_sf6;
typedef hashed_unique<person_name> idx_sf7; //散列多键,使用自定义键提取器(基于m_fname+m_lname的不允许重复的无序集合)
int main()
{
typedef multi_index_container<person,
indexed_by<
idx_sf0,idx_sf1, //序列索引和随机访问索引
idx_sf2,idx_sf3,idx_sf4, //有序索引
idx_sf5,idx_sf6,idx_sf7> //散列索引
> mic_t;
using namespace boost::assign;
mic_t mic;
push_back(mic)
(person(2,"agent","smith")) //成功
(person(20,"lee","someone")) //成功
(person(1,"anderson","neo")) //成功
(person(10,"lee","bruce")); //因为m_fname重复所以插入失败
assert(mic.size() == 3); //只插入了3个元素
auto& idx1 = mic.get<rnd_idx>(); //获取随机访问索引
auto& idx2 = mic.get<2>(); //获取有序索引
auto pos = idx2.find(1,compare_by_id()); //在有序索引中查找元素
auto pos2 = mic.project<string>(pos); //投射到随机访问索引
assert(pos2 == idx1.iterator_to(idx1[2]));
return 0;
}
複合インデックスキー
データベースの結合主キーの概念と同様に、1 つのキーだけを使用して要素を並べ替えるだけでは不十分な場合があります。複数のキーに基づいて要素を検索する必要があります。multi_index ライブラリは、複数のキーを組み合わせることができる複合インデックス キー complex_key を提供します。キー エクストラクターを新しいキーに追加します。インデックス使用の場合、ヘッダー ファイル <boost/multi_index/composite_key.hpp> にあります
。composite_key は、boost.tuple に基づいて複数のキー エクストラクターの組み合わせを実装する読み取り専用の抽出キーです。その最初のテンプレートパラメータは要素タイプの値であり、その後に 1 つ以上の複合キー抽出子を続けることができます。これらのエクストラクターは、identity、member、const_mem_fun などにすることができますが、要素タイプとして Value も使用する必要があります。現在、最大 10 個のキー エクストラクターの組み合わせがサポートされています。
COMPOSITE_KEY の Operator() の戻り値の型は、composite_key_result です。これは、標準の比較述語、equal_to、less、greater、および boost のハッシュ演算をオーバーロードする単純な構造体であり、タプルとの比較演算をサポートしており、基本型と同様に使用できます。
使用法:
complex_key は、順序付けされたインデックスとハッシュ インデックスに使用できます。たとえば、composite_key を使用して、m_id と m_fname に基づいて複合インデックス キーを定義します。要素タイプではポインタが使用されることに注意してください。つまり、ポインタ タイプはコンテナに格納される必要があります。 :
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/assign.hpp>
#include <boost/assign/ptr_list_inserter.hpp> //for ptr_push_back
#include <boost/tuple/tuple.hpp>
#include <boost/functional/hash.hpp> //hash_combine函数
#include <string>
using namespace boost::multi_index;
using namespace std;
class person :
boost::less_than_comparable<person> //使用operators库实现全序比较
{
public:
int m_id;
std::string m_fname, m_lname;
person(int id, const std::string& f, const std::string& l) :
m_id(id), m_fname(f), m_lname(l) {}
const std::string& first_name() const { return m_fname; }
std::string& last_name() { return m_lname; }
friend bool operator<(const person& l,const person& r)
{
return l.m_id < r.m_id;
}
friend bool operator==(const person& l,const person& r)
{
return l.m_id == r.m_id;
}
friend std::size_t hash_value(const person& p)
{
size_t seed = 2016;
boost::hash_combine(seed, p.m_fname);
boost::hash_combine(seed, p.m_lname);
return seed;
}
};
typedef composite_key<person*, //元素类型为指针
BOOST_MULTI_INDEX_MEMBER(person,int,m_id),
BOOST_MULTI_INDEX_MEMBER(person,string,m_fname)
> comp_key;
int main()
{
typedef multi_index_container<
person*, //元素类型同样也是指针
indexed_by<
ordered_unique<comp_key>
>
> mic_t;
//使用指针容器来存储元素,把多索引容器当作指针容器的一个视图来使用,避免手工删除指针的麻烦
boost::ptr_vector<person> vec;
using namespace boost::assign;
ptr_push_back(vec)
(2,"agent","smith")
(1,"anderson","neo")
(1,"the one","neo"); //id重复
mic_t mic;
for(auto& p : vec) //插入指针元素到多索引容器
{
mic.insert(&p);
}
for(auto p : mic) //顺序输出多索引容器内的元素
{
std::cout << p->m_id << ":" << p->m_fname << ",";
}
//find()和count()等涉及查找操作时需要使用tuple来构造查找值
assert(mic.count(boost::make_tuple(1,"anderson")) == 1);
assert(mic.find(boost::make_tuple(2,"agent")) != mic.end());
//有序索引中可以仅顺序指定部分键值,这样索引将执行模糊查找,仅对指定的查找值执行比较
assert(mic.count(boost::make_tuple(1)) == 2); //仅指定一个键
//使用第一个键,可以不使用tuple
assert(mic.count(1) == 2);
//对于散列索引不能指定部分键值,因为散列必须基于所有的键
return 0;
}
1 つのキーで昇順を実行し、別のキーで降順を実行するなど、複合キーでカスタマイズされた作業を実行する場合、multi_index ライブラリは、composite_key_result を操作する複数の比較述語とハッシュ関数オブジェクトを提供します。
3 つの基本関数オブジェクトを使用すると、個々のキーに基づいて比較述語またはハッシュ関数オブジェクトを組み合わせることで、並べ替え基準を任意にカスタマイズできます。
template<typename Pred0,...,typename Predn>
struct composite_key_equal_to;
template<typename Compare0,...,typename Comparen>
struct composite_key_compare;
template<typename Hash0,...,typename Hashn>
struct composite_key_hash;
person クラスの m_id を昇順で並べ替え、m_fname を降順で並べ替える必要があると仮定します。カスタマイズされた並べ替え基準は次のとおりです。
typedef composite_key_compare<
std::less<int>, //m_id升序
std::greater<string> //m_fname降序
> comp_key_compare;
マルチインデックスコンテナを定義するときは、結合された比較述語の定義を追加する必要があります。
typedef multi_index_container<
person*,
indexed_by<
ordered_unique<
comp_key, //组合键
comp_key_compare> //组合比较谓词
>
> mic_t;
組み合わせた比較述語の使用を容易にするために、multi_index ライブラリは 4 つの簡略化された関数オブジェクトを定義し、標準ライブラリのqual_to、less、greater、および boost.hash を使用してすべてのキーを操作します。
template<typename CompositeKeyResult>
struct composite_key_result_equal_to;
template<typename CompositeKeyResult>
struct composite_key_result_less;
template<typename CompositeKeyResult>
struct composite_key_result_greater;
template<typename CompositeKeyResult>
struct composite_key_result_hash;
m_id と m_fname の両方を降順に並べ替える場合は、次のように直接定義できます。
typedef multi_index_container<
person*,
indexed_by<
ordered_unique<
comp_key,
composite_key_result_greater<comp_key::result_type>
>
>
> mic_t;