Fixes for advanced lookup and insertions documentation.

This commit is contained in:
Ion Gaztañaga
2015-10-08 11:11:29 +02:00
parent b11720f527
commit bba1782654
2 changed files with 80 additions and 68 deletions

View File

@@ -1945,75 +1945,109 @@ the same options explained in the section
[*Boost.Intrusive] associative containers offer an interface similar to STL associative [*Boost.Intrusive] associative containers offer an interface similar to STL associative
containers. However, STL's ordered and unordered simple associative containers containers. However, STL's ordered and unordered simple associative containers
(`std::set`, `std::multiset`, `std::unordered_set` and `std::unordered_multiset`) (`std::set`, `std::multiset`, `std::unordered_set` and `std::unordered_multiset`)
have some inefficiencies caused by the interface in several search functions have some inefficiencies caused by the interface in several search, insertion or erasure functions
(`equal_range`, `lower_bound`, `upper_bound`, ...): the user can only operate with (`equal_range`, `lower_bound`, `upper_bound`, `find`, `insert`, `erase`...): the user can only operate
`value_type` objects or (starting from C++11), with `value_type` objects or (starting from C++11),
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3657.htm heterogeneous comparison lookups] [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3657.htm heterogeneous comparison lookups]
which are not flexible enough as `key_compare` shall support the comparison between the provided which are not flexible enough as `key_compare` shall support the comparison between the provided
key and `value_type`, avoiding the use of user-defined comparison objects that can partition the key and `value_type`, which precludes the use of user-defined comparison objects that can partition the
search in advanced ways. search in a compatible but advanced way.
Imagine that the object to be searched is quite expensive to construct: To solve these problems, [*Boost.Intrusive] containers offers functions where a key type different
from `key_type` and a comparison object are provided by the user. This applies to:
* equal_range
* lower_bound
* upper_bound
* count
* find
* erase
Requirements for such functions are:
* For unordered container the provided comparison and hashing
function with the given key shall induce the same hash and equivalence as `key_compare` and `hasher`.
* For ordered associative containers, lookup and erasure functions, the container to be searched shall
be partitioned in regards to the supplied comparison object and key.
For more details, see [*Requires] clauses of such functions in the reference.
[section:advanced_lookups_example Example]
Imagine that the object to be searched is quite expensive to construct (called `Expensive` in the example):
[import ../example/doc_assoc_optimized_code.cpp] [import ../example/doc_assoc_optimized_code.cpp]
[doc_assoc_optimized_code_normal_find] [doc_assoc_optimized_code_normal_find]
`Expensive` is an expensive object to construct. If "key" c-string is quite long If "key" c-string is quite long
`Expensive` has to construct a `std::string` using heap memory. Like `Expensive` has to construct a `std::string` using heap memory. Like
`Expensive`, many times the only member taking part in ordering issues is just `Expensive`, many times the only member taking part in ordering issues is just
a small part of the class. For example, with `Expensive`, only the internal a small part of the class. E.g.: with `Expensive`, only the internal
`std::string` is needed to compare the object. `std::string` is needed to compare the object.
In both containers, if we call `get_from_set/get_from_unordered_set` in a loop, we might get a performance penalty, In both containers, if we call `get_from_set/get_from_unordered_set` in a loop, we might get a performance penalty,
because we are forced to create a whole `Expensive` object to be able to find an because we are forced to create a whole `Expensive` object to be able to find an
equivalent one. equivalent one.
Sometimes this interface limitation is severe, because Sometimes the problem is not only performance-related, as
we [*might not have enough information to construct the object] but we might we [*might not have enough information to construct the object] but we might
[*have enough information to find the object]. In this case, a name is enough [*have enough information to find the object]. In this case, a name is enough
to search `Expensive` in the container but constructing an `Expensive` object to search `Expensive` objects in the container but constructing an `Expensive`
might requires more information that the user might not have. object might require more information that the searcher might not have.
To solve this, [*Boost.Intrusive] associative containers To solve this, we can use the functions that take any type comparable with the value and a
offer alternative functions, which take any type comparable with the value and a the 'compatible' comparison object (and hash, when the associative container is unordered)
functor that should be `compatible` Let's see optimized search function:
(the associative container must be also partitioned in regards to the supplied comparison type)
with container's predicate function (`key_compare`).
[classref boost::intrusive::unordered_set unordered_set]/[classref boost::intrusive::unordered_multiset unordered_multiset]
offer similar functions that take any key type, a compatible hash (the hash of the key) and a equality function. Now, let's see
optimized search function:
[doc_assoc_optimized_code_optimized_find] [doc_assoc_optimized_code_optimized_find]
This new arbitrary key overload is also available for other functions taking [endsect]
values as arguments:
* equal_range
* lower_bound
* upper_bound
* count
* find
* erase
Check [classref boost::intrusive::set set],
[classref boost::intrusive::multiset multiset],
[classref boost::intrusive::unordered_set unordered_set],
[classref boost::intrusive::unordered_multiset unordered_multiset]
references to know more about those functions.
[endsect] [endsect]
[section:advanced_insertions Advanced insertions] [section:advanced_insertions Advanced insertions]
A similar issue happens with insertions in simple ordered and unordered associative A similar issue happens with insertions in simple ordered and unordered associative
containers with unique keys (`std::set` and `std::tr1::unordered_set`). In these containers, containers with unique keys (`std::set` and `std::unordered_set`). In these containers,
if a value is already present, the value to be inserted is discarded. With expensive if a value is already present, the value to be inserted is discarded. With expensive
values, if the value is already present, we can suffer efficiency problems. values, if the value is already present, we can suffer efficiency problems.
[classref boost::intrusive::set set] and [classref boost::intrusive::unordered_set unordered_set] [classref boost::intrusive::set set] and [classref boost::intrusive::unordered_set unordered_set]-like
have insertion functions to check efficiently, without containers have insertion functions (`insert_check`, `insert_unique_check`,...) to check efficiently, without
constructing the value, if a value is present or not and if it's not present, a constructing the value, if a value is present or not and if it's not present, a
function to insert it immediately without any further lookup. function to insert it immediately (`insert_commit`) without any further lookup. Requirements for functions
that check the existence of such value are:
* For unordered container the provided comparison and hashing
function with the given key shall induce the same hash and equivalence as `key_compare` and `hasher`.
* For ordered associative containers, the provided comparison function with the given key, shall induce the same
strict weak order as `key_compare`.
To sum up, `insert_check` is similar to a normal `insert` but:
* `insert_check` can be used with arbitrary keys
* if the insertion is possible (there is no equivalent value) `insert_check` collects all the needed information
in an `insert_commit_data` structure, so that `insert_commit`:
* [*does not execute] further comparisons
* can be executed with [*constant-time complexity]
* has [*no-throw guarantee].
These functions must be used with care,
no other insertion or erasure must be executed between an `insert_check` and an `insert_commit`
pair. Otherwise, the behaviour is undefined.
See [classref boost::intrusive::set set]
and [classref boost::intrusive::unordered_set unordered_set]-like containers' reference
for more information about `insert_check` and `insert_commit`.
With multiple ordered and unordered associative containers
([classref boost::intrusive::multiset multiset] and
[classref boost::intrusive::unordered_multiset unordered_multiset]) there is
no need for these advanced insertion functions, since insertions are always successful.
[section:advanced_insertions_example Example]
For example, using the same `Expensive` class, For example, using the same `Expensive` class,
this function can be inefficient: this function can be inefficient:
@@ -2025,28 +2059,7 @@ will be discarded, and this is a waste of resources. Instead of that, let's use
[doc_assoc_optimized_code_optimized_insert] [doc_assoc_optimized_code_optimized_insert]
`insert_check` is similar to a normal `insert` but: [endsect]
* `insert_check` can be used with arbitrary keys
* if the insertion is possible (there is no equivalent value) `insert_check` collects all the needed information
in an `insert_commit_data` structure, so that `insert_commit`:
* [*does not execute] further comparisons
* can be executed with [*constant-time complexity]
* has [*no-throw guarantee].
These functions must be used with care, since
no other insertion or erasure must be executed between an `insert_check` and an `insert_commit`
pair. Otherwise, the behaviour is undefined.
`insert_check` and `insert_commit` will come in handy
for developers programming efficient non-intrusive associative containers.
See [classref boost::intrusive::set set]
and [classref boost::intrusive::unordered_set unordered_set] reference for more information about
`insert_check` and `insert_commit`.
With multiple ordered and unordered associative containers
([classref boost::intrusive::multiset multiset] and
[classref boost::intrusive::unordered_multiset unordered_multiset]) there is
no need for these advanced insertion functions, since insertions are always successful.
[endsect] [endsect]
@@ -2062,8 +2075,8 @@ from other associative containers: if the ordering and uniqueness properties are
there is no need to waste time checking the position of each source value, because values there is no need to waste time checking the position of each source value, because values
are already ordered: back insertions will be much more efficient. are already ordered: back insertions will be much more efficient.
[*Note:] These functions [*don't check preconditions] so they must used with care. These [*Note:] These functions [*don't check preconditions] so they must used with care. They
are functions are low-level operations [*will break container invariants if are low-level operations that [*will break container invariants if
ordering and uniqueness preconditions are not assured by the caller.] ordering and uniqueness preconditions are not assured by the caller.]
Let's see an example: Let's see an example:
@@ -2071,7 +2084,6 @@ Let's see an example:
[import ../example/doc_positional_insertion.cpp] [import ../example/doc_positional_insertion.cpp]
[doc_positional_insertion] [doc_positional_insertion]
[endsect] [endsect]
For more information about advanced lookup and insertion functions see For more information about advanced lookup and insertion functions see

View File

@@ -1386,7 +1386,7 @@ class bstree_impl
{ return this->erase(key, this->key_comp()); } { return this->erase(key, this->key_comp()); }
//! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to //! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to
//! comp(nk, key) and !c(key, nk), with c(nk, key) implying !c(key, nk), //! comp(nk, key) and !comp(key, nk), with comp(nk, key) implying !comp(key, nk),
//! with nk the key_type of a value_type inserted into `*this`. //! with nk the key_type of a value_type inserted into `*this`.
//! //!
//! <b>Effects</b>: Erases all the elements with the given key. //! <b>Effects</b>: Erases all the elements with the given key.
@@ -1470,7 +1470,7 @@ class bstree_impl
{ size_type n; return this->private_erase(b, e, n, disposer); } { size_type n; return this->private_erase(b, e, n, disposer); }
//! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to //! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to
//! comp(nk, key) and !c(key, nk), with c(nk, key) implying !c(key, nk) //! comp(nk, key) and !comp(key, nk), with comp(nk, key) implying !comp(key, nk)
//! and nk the key_type of a value_type inserted into `*this`. //! and nk the key_type of a value_type inserted into `*this`.
//! //!
//! <b>Requires</b>: Disposer::operator()(pointer) shouldn't throw. //! <b>Requires</b>: Disposer::operator()(pointer) shouldn't throw.
@@ -1546,7 +1546,7 @@ class bstree_impl
{ return size_type(this->count(key, this->key_comp())); } { return size_type(this->count(key, this->key_comp())); }
//! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to //! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to
//! comp(nk, key) and !c(key, nk), with c(nk, key) implying !c(key, nk), //! comp(nk, key) and !comp(key, nk), with comp(nk, key) implying !comp(key, nk),
//! and nk the key_type of a value_type inserted into `*this`. //! and nk the key_type of a value_type inserted into `*this`.
//! //!
//! <b>Effects</b>: Returns the number of contained elements with the given key //! <b>Effects</b>: Returns the number of contained elements with the given key
@@ -1643,7 +1643,7 @@ class bstree_impl
iterator find(const key_type &key); iterator find(const key_type &key);
//! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to //! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to
//! comp(nk, key) and !c(key, nk), with c(nk, key) implying !c(key, nk), //! comp(nk, key) and !comp(key, nk), with comp(nk, key) implying !comp(key, nk),
//! and nk the key_type of a value_type inserted into `*this`. //! and nk the key_type of a value_type inserted into `*this`.
//! //!
//! <b>Effects</b>: Finds an iterator to the first element whose key is //! <b>Effects</b>: Finds an iterator to the first element whose key is
@@ -1672,7 +1672,7 @@ class bstree_impl
std::pair<iterator,iterator> equal_range(const key_type &key); std::pair<iterator,iterator> equal_range(const key_type &key);
//! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to //! <b>Requires</b>: key is a value such that `*this` is partitioned with respect to
//! comp(nk, key) and !c(key, nk), with c(nk, key) implying !c(key, nk), //! comp(nk, key) and !comp(key, nk), with comp(nk, key) implying !comp(key, nk),
//! with nk the key_type of a value_type inserted into `*this`. //! with nk the key_type of a value_type inserted into `*this`.
//! //!
//! <b>Effects</b>: Finds a range containing all elements whose key is k or //! <b>Effects</b>: Finds a range containing all elements whose key is k or