MultiIndexの要素書き換え

replaceとmodifyの違いがわからなかったので、そのあたりのドキュメントを訳しました。


Updating - Boost.MultiIndex Documentation


replaceメンバ関数は、以下の例が示すように特定要素の置き換えを行います。

typedef index<employee_set, name>::type employee_set_by_name; 
employee_set_by_name& name_index = es.get<name>(); 

employee_set_by_name::iterator it = name_index.find("Anna Jones"); 
employee anna = *it; 
anna.name = "Anna Smith";     // 彼女はCalvin Smithと結婚した
name_index.replace(it, anna); // レコードを更新

replaceは以下の方法でこの置き換えを行います。

  • 変更された要素がすべてのインデックスリストに関してオリジナルのオーダーを保持する場合、計算量は定数時間で、そうでなければ対数時間
  • イテレータと参照の有効性は保たれる
  • 操作は強い例外安全、つまり、(システムまたはユーザーのデータ型によって引き起こされる)何らかの例外が投げられた場合、multi_index_containerは変化しない

replaceは、標準のSTLコンテナで提供されなかった強力な操作と、強い例外安全が必要であるときに特に便利です。


注意深い読者は、replaceの便利さにコストがあることに気づいたかもしれません:すなわち、更新(と内部のreplaceを検索)のために2回全体の要素をコピーしなければなりません。要素をコピーすることが高価な場合、これはまさしくオブジェクトの小さな部分変更のためのかなりの計算コストであるかもしれません。この状況に対処するために、Boost.MultiIndexはmodifyと呼ばれる更新手段を提供します:

struct change_name {
    change_name(const std::string& new_name) : new_name(new_name) {}

    void operator()(employee& e)
    {
        e.name = new_name;
    }

private:
    std::string new_name;
};

...

typedef employee_set::index<name>::type employee_set_by_name;
employee_set_by_name& name_index = es.get<name>();

employee_set_by_name::iterator it = name_index.find("Anna Jones");
name_index.modify(it, change_name("Anna Smith"))

modifyは、変更される要素への参照が渡されるファンクタ(あるいは関数ポインタ)を受け取り、その結果、偽のコピーの必要性をなくします。replaceのように、modifyはmulti_index_containerの全てのインデックスリストの内部オーダーを維持します。


しかし、modifyのセマンティクスはreplaceと完全に等価ではありません。要素を変更する結果として衝突(つまり、あるunique ordered indexに関する他の変更された要素との衝突)が起こる場合に何が起きるか考えてみましょう。replaceの場合には、元の値が維持され、メソッドはコンテナは変更されずに返りますが、modifyでは変更ファンクタが要素の前値の形跡を残さないため、そのようなアプローチは提供できません。


その結果、一貫性制約(integrity constraint)は、以下の方針をもたらします:衝突がmodifyを呼ぶプロセスで発生するとき、要素は消去され、メソッドはfalseを返します。衝突した場合には変更を元に戻すためにロールバックファンクタを受け入れるmodifyのもう一つのバージョンがあります:

struct change_id { 
   change_id(int new_id) : new_id(new_id) {} 

   void operator()(employee& e) 
   { 
       e.id = new_id; 
   } 

private: 
   int new_id; 
}; 

... 

employee_set::iterator it = ... 

int old_id = it->id; // 元の値を維持する 

// idの変更を試みて、衝突した場合にはそれ復旧する 
es.modify(it, change_id(321), change_id(old_id));

この例では、change_id(old_id)は修正によって他の要素と衝突した場合、元の状態に回復するために呼び出されます。プログラマは、最良の更新メカニズムを決定するためにreplace、modifyおよびロールバックをもつmodifyの振る舞いの差をケースバイケースで考慮しなければなりません。


更新関数と衝突時の動作

  • replace(it, x):置換は行われない
  • modify(it, mod):要素は消去される
  • modify(it, mod, back):backは復旧するのに使用される(backが例外を投げた場合、要素は消去される)


modify_keyという名前のmodifyのキーに基づいたバージョンも同様に提供されます。この場合、変更ファンクタは全体のオブジェクトの代わりに要素のkey_type部分に参照が渡されます。

struct change_str { 
   change_str(const std::string& new_str) : new_str(new_str) {} 

   // employeeではなくstringが渡されることに注意 
   void operator()(std::string& str) 
   { 
       str = new_str; 
   } 

private: 
   std::string new_str; 
}; 

... 

typedef employee_set::index<name>::type employee_set_by_name; 
employee_set_by_name& name_index = es.get<name>(); 

employee_set_by_name::iterator it = name_index.find("Anna Jones"); 
name_index.modify_key(it, change_str("Anna Smith")); 

modifyのように、ロールバックを持つバージョンと持たないバージョンのmodify_keyがあります。modifyとmodify_keyは、変更ファンクタの定義のためにBoost.Lambdaを使用するのが適しています。

using namespace boost::lambda; 

typedef employee_set::index<name>::type employee_set_by_name; 
employee_set_by_name& name_index = es.get<name>(); 

employee_set_by_name::iterator it = name_index.find("Anna Jones"); 
name_index.modify_key(it, _1="Anna Smith");

modify_keyは、key extractorがread/writeと呼ばれる特別な型であることを必要とします:通常、しかし常にではないケース。