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
をオーバーライドする必要があります(自分で作成する方法はわかりません)。
set
はconst_iterators
を返します(標準ではset<T>::iterator
はconst
であり、set<T>::const_iterator
とset<T>::iterator
は実際には同じ型であるとしています-注文されたコンテナであるため、n3000.pdfの23.2.4/6を参照してください。通常のiterator
が返された場合は、アイテムの値をコンテナの下から変更できるため、順序が変更される可能性があります。
あなたの解決策は、set
の項目を変更する慣用的な方法です。
簡単な場合、これを行うには2つの方法があります。
mutable
を使用できますKey
Value
ペアに分割できます(std::map
)さて、問題はトリッキーなケースです:更新が実際にオブジェクトのkey
部分を変更するとどうなりますか?あなたのアプローチはうまくいきますが、退屈だと私は認めます。
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));
}
更新:以下は現時点で当てはまりますが、動作は defect と見なされ、次のバージョンで変更されます標準の。とても悲しい。
あなたの質問をかなり混乱させるいくつかのポイントがあります。
std::set
_はクラスであるため、何も返すことができません。s.erase(iter)
を呼び出すことができる場合、iter
は_const_iterator
_ではありません。 erase
には非constイテレータが必要です。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;
}
_
更新によって要素の順序が変更された場合は、消去して再挿入する必要があります。
std::map
代わりに。キーの順序に影響するElement
の部分を使用し、Element
のすべてを値として入力します。いくつかのマイナーなデータの重複がありますが、より簡単に(そしておそらくより高速に)更新されます。
変換が::std::set<T>::iterator
不変式に影響を及ぼさないことがわかっている場合でも、実際に<
は定数であり、その内容を変更できないというC++ 11でも同じ問題が発生しました。これを回避するには、::std::set
をmutable_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%で機能します。
これはいくつかのケースでより高速です:
std::pair<std::set<int>::iterator, bool> result = Set.insert(value);
if (!result.second) {
Set.erase(result.first);
Set.insert(value);
}
通常、値がstd::set
にない場合は、パフォーマンスが向上する可能性があります。