[multi_index] do erase() call key extractors?
This is probably rather obvious, but I wasn't able to locate this guarantee
in MIC docs. Take,
struct Foo {
explicit Foo(Bar* b = nullptr) : bar(b) {}
Bar* bar;
};
int getBarValue (const Foo& f) {
assert( f.bar );
return f.bar->getValue();
}
boost::multi_index< Foo, indexed_by<
< hashed_unique < member
FooIndex;
void grill() { FooIndex fooIndex; Bar* b; { auto bWork = make_unique<Bar>(); // ... fooIndex.emplace( bWork.get() ); b = bWork.get(); } fooIndex.erase(b); // ... } Please ignore the fact that raw-pointers are potentially dangerous etc. The question is does erase() call getBarValue() despite the fact I am using the first index? More generally, do key extractors get called *at all* except at the point of insertion? Thanks, Nick
Nick Stokes
This is probably rather obvious, but I wasn't able to locate this guarantee in MIC docs. Take,
struct Foo { explicit Foo(Bar* b = nullptr) : bar(b) {} Bar* bar; };
int getBarValue (const Foo& f) { assert( f.bar ); return f.bar->getValue(); }
boost::multi_index< Foo, indexed_by< hashed_unique < member
>> , hashed_non_unique < global_fun > FooIndex;
void grill() { FooIndex fooIndex;
Bar* b; { auto bWork = make_unique<Bar>(); // ... fooIndex.emplace( bWork.get() ); b = bWork.get(); } fooIndex.erase(b);
// ... }
Please ignore the fact that raw-pointers are potentially dangerous etc. The question is does erase() call getBarValue() despite the fact I am using the first index?
No. No key extractor or any other user-provided functor (compare predicate, hash function etc.) is ever used when erasing an element by iterator, regardless of the index you're using to do it. If you take a look at the docs for erase(iterator) at http://www.boost.org/libs/multi_index/doc/ reference/hash_indices.html#modifiers you'll see the exception safery if guaranteedly "nothrow", which means erase can't invoke any possibily-throwing user-provided function. This applies to all indices of Boost.MultiIndex. erase(const key_type&), on the other hand, needs to determine the elements to be erased by key extraction, hashing and comparing for equality (in the case of hashed indices) or key extraction and comparing for inequality (ordered indices), which may throw as recognized by the exception safety clause in the definition, marked as "basic".
More generally, do key extractors get called *at all* except at the point of insertion?
Look for exception safety guarantees in the docs, where "nothrow" is provided you know no user-provided function is called. In particular, for hashed indices key extraction, hashing and comparison for equality is only used at * Insertion/emplacement * replace/modify * Lookup * rehash/reserve * Serialization loading (this and saving are not actually clear from docs) Joaquín M López Muñoz Telefónica
On Fri, Dec 25, 2015 at 5:31 AM, Joaquin M Lopez Munoz
Nick Stokes
writes: This is probably rather obvious, but I wasn't able to locate this guarantee in MIC docs. Take,
struct Foo { explicit Foo(Bar* b = nullptr) : bar(b) {} Bar* bar; };
int getBarValue (const Foo& f) { assert( f.bar ); return f.bar->getValue(); }
boost::multi_index< Foo, indexed_by< hashed_unique < member
>> , hashed_non_unique < global_fun > FooIndex;
void grill() { FooIndex fooIndex;
Bar* b; { auto bWork = make_unique<Bar>(); // ... fooIndex.emplace( bWork.get() ); b = bWork.get(); }
fooIndex.erase(b);
// ... }
Please ignore the fact that raw-pointers are potentially dangerous etc. The question is does erase() call getBarValue() despite the fact I am using the first index?
No. No key extractor or any other user-provided functor (compare predicate, hash function etc.) is ever used when erasing an element by iterator, regardless of the index you're using to do it. If you take a look at the docs for erase(iterator) at
http://www.boost.org/libs/multi_index/doc/ reference/hash_indices.html#modifiers
you'll see the exception safery if guaranteedly "nothrow", which means erase can't invoke any possibily-throwing user-provided function. This applies to all indices of Boost.MultiIndex. erase(const key_type&), on the other hand, needs to determine the elements to be erased by key extraction, hashing and comparing for equality (in the case of hashed indices) or key extraction [...]
Thank you very much Joaquín. With your explanation clarifying the nothrow, the docs are very clear indeed. I guess my question is a little more subtle. I think I understand now, but please let me reaffirm: In my example, erase(const key_type&) is being called for the 1-st index (key_type is Bar*), which *could* potentially throw, but in this case this is a simple pointer value and hashing and equality and therefore do *not* throw. But to adjust the 2nd index, MIC does not require the key_extraction on that (which would throw, or in fact, segv), because the node is already identified and is erased. Is that correct? Thanks, Nick
Nick Stokes
[...]
struct Foo { explicit Foo(Bar* b = nullptr) : bar(b) {} Bar* bar; };
int getBarValue (const Foo& f) { assert( f.bar ); return f.bar->getValue(); }
boost::multi_index< Foo, indexed_by< hashed_unique < member
>> , hashed_non_unique < global_fun > FooIndex;
void grill() { FooIndex fooIndex;
Bar* b; { auto bWork = make_unique<Bar>(); // ... fooIndex.emplace( bWork.get() ); b = bWork.get(); } fooIndex.erase(b);
// ... }
[...]
I guess my question is a little more subtle.
Yep, now on rereading my answer I see I jumped too fast to a canonical explanation :-)
I think I understand now, but please let me reaffirm: In my example, erase(const key_type&) is being called for the 1-st index (key_type is Bar*), which *could* potentially throw, but in this case this is a simple pointer value and hashing and equality and therefore do *not* throw. But to adjust the 2nd index, MIC does not require the key_extraction on that (which would throw, or in fact, segv), because the node is already identified and is erased. Is that correct?
fooIndex.erase(b) involves hashing and equality comparison for Bar*'s, which as you correctly point out does not throw. Once the element(s) to be erased are identified, no further user-provided function (getBarValue or any other) is invoked, and deletion will succeed. Briefly put, fooIndex.erase(b) does not ever throw and does not ever call getBarValue, if this is what you were after. Joaquín M López Muñoz Telefónica
[..] fooIndex.erase(b) involves hashing and equality comparison for Bar*'s, which as you correctly point out does not throw. Once the element(s) to be erased are identified, no further user-provided function (getBarValue or any other) is invoked, and deletion will succeed. Briefly put, fooIndex.erase(b) does not ever throw and does not ever call getBarValue, if this is what you were after.
Precisely! Thanks, nick
participants (2)
-
Joaquin M Lopez Munoz
-
Nick Stokes