web-dev-qa-db-ja.com

C ++ std :: set updateは退屈です:適切な要素を変更できません

cppreference にはそのようなAPIがないため、std::setでの更新操作は面倒です。だから私が現在やっていることは次のようなものです:

//find element in set by iterator
Element copy = *iterator;
... // update member value on copy, varies
Set.erase(iterator);
Set.insert(copy);

基本的に、Setが返すイテレータはconst_iteratorであり、その値を直接変更することはできません。

これを行うより良い方法はありますか?または、自分でstd::setをオーバーライドする必要があります(自分で作成する方法はわかりません)。

68
Figo

setconst_iteratorsを返します(標準ではset<T>::iteratorconstであり、set<T>::const_iteratorset<T>::iteratorは実際には同じ型であるとしています-注文されたコンテナであるため、n3000.pdfの23.2.4/6を参照してください。通常のiteratorが返された場合は、アイテムの値をコンテナの下から変更できるため、順序が変更される可能性があります。

あなたの解決策は、setの項目を変更する慣用的な方法です。

72
Terry Mahaffey

簡単な場合、これを行うには2つの方法があります。

  • キーの一部ではない変数でmutableを使用できます
  • クラスをKeyValueペアに分割できます(std::map

さて、問題はトリッキーなケースです:更新が実際にオブジェクトのkey部分を変更するとどうなりますか?あなたのアプローチはうまくいきますが、退屈だと私は認めます。

22
Matthieu M.

C++ 17では、 P008 のおかげで、 extract() を使用すると、より良い結果が得られます。

// remove element from the set, but without needing
// to copy it or deallocate it
auto node = Set.extract(iterator);
// make changes to the value in place
node.value() = 42;
// reinsert it into the set, but again without needing 
// to copy or allocate
Set.insert(std::move(node));

これにより、タイプの余分なコピーと追加の割り当て/割り当て解除が回避され、移動のみのタイプでも機能します。

キーでextractすることもできます。キーが存在しない場合、これは空のノードを返します:

auto node = Set.extract(key);
if (node) // alternatively, !node.empty()
{
    node.value() = 42;
    Set.insert(std::move(node));
}
8
Barry

更新:以下は現時点で当てはまりますが、動作は defect と見なされ、次のバージョンで変更されます標準の。とても悲しい。


あなたの質問をかなり混乱させるいくつかのポイントがあります。

  1. 関数は値を返すことができますが、クラスは返すことができません。 _std::set_はクラスであるため、何も返すことができません。
  2. s.erase(iter)を呼び出すことができる場合、iterは_const_iterator_ではありません。 eraseには非constイテレータが必要です。
  3. イテレータを返す_std::set_のすべてのメンバー関数は、セットが非constである限り、非constイテレータを返します。

更新によって要素の順序が変更されない限り、セットの要素の値を変更できます。次のコードはコンパイルして問題なく動作します。

_#include <set>

int main()
{
    std::set<int> s;
    s.insert(10);
    s.insert(20);

    std::set<int>::iterator iter = s.find(20);

    // OK
    *iter = 30;

    // error, the following changes the order of elements
    // *iter = 0;
}
_

更新によって要素の順序が変更された場合は、消去して再挿入する必要があります。

8
avakar

std::map代わりに。キーの順序に影響するElementの部分を使用し、Elementのすべてを値として入力します。いくつかのマイナーなデータの重複がありますが、より簡単に(そしておそらくより高速に)更新されます。

7
user268238

変換が::std::set<T>::iterator不変式に影響を及ぼさないことがわかっている場合でも、実際に<は定数であり、その内容を変更できないというC++ 11でも同じ問題が発生しました。これを回避するには、::std::setmutable_setタイプにラップするか、コンテンツのラッパーを記述します。

  template <typename T>
  struct MutableWrapper {
    mutable T data;
    MutableWrapper(T const& data) : data(data) {}
    MutableWrapper(T&& data) : data(data) {}
    MutableWrapper const& operator=(T const& data) { this->data = data; }
    operator T&() const { return data; }
    T* operator->() const { return &data; }
    friend bool operator<(MutableWrapper const& a, MutableWrapper const& b) {
      return a.data < b.data;
    }   
    friend bool operator==(MutableWrapper const& a, MutableWrapper const& b) {
      return a.data == b.data;
    }   
    friend bool operator!=(MutableWrapper const& a, MutableWrapper const& b) {
      return a.data != b.data;
    }   
  };

私はこれがはるかに単純であるとわかり、セットと実際のタイプの間に何かがあることにユーザーが気付くことなく、ケースの90%で機能します。

2
bitmask

これはいくつかのケースでより高速です:

std::pair<std::set<int>::iterator, bool> result = Set.insert(value);
if (!result.second) {
  Set.erase(result.first);
  Set.insert(value);
}

通常、値がstd::setにない場合は、パフォーマンスが向上する可能性があります。

0
jcoffland