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
containers. However, STL's ordered and unordered simple associative containers
(`std::set`, `std::multiset`, `std::unordered_set` and `std::unordered_multiset`)
have some inefficiencies caused by the interface in several search functions
(`equal_range`, `lower_bound`, `upper_bound`, ...): the user can only operate with
`value_type` objects or (starting from C++11),
have some inefficiencies caused by the interface in several search, insertion or erasure functions
(`equal_range`, `lower_bound`, `upper_bound`, `find`, `insert`, `erase`...): the user can only operate
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]
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
search in advanced ways.
key and `value_type`, which precludes the use of user-defined comparison objects that can partition the
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]
[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`, 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.
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
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
[*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
might requires more information that the user might not have.
to search `Expensive` objects in the container but constructing an `Expensive`
object might require more information that the searcher might not have.
To solve this, [*Boost.Intrusive] associative containers
offer alternative functions, which take any type comparable with the value and a
functor that should be `compatible`
(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:
To solve this, we can use the functions that take any type comparable with the value and a
the 'compatible' comparison object (and hash, when the associative container is unordered)
Let's see optimized search function:
[doc_assoc_optimized_code_optimized_find]
This new arbitrary key overload is also available for other functions taking
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]
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
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]
have insertion functions to check efficiently, without
[classref boost::intrusive::set set] and [classref boost::intrusive::unordered_set unordered_set]-like
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
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,
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]
`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, 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
are already ordered: back insertions will be much more efficient.
[*Note:] These functions [*don't check preconditions] so they must used with care. These
are functions are low-level operations [*will break container invariants if
[*Note:] These functions [*don't check preconditions] so they must used with care. They
are low-level operations that [*will break container invariants if
ordering and uniqueness preconditions are not assured by the caller.]
Let's see an example:
@@ -2071,7 +2084,6 @@ Let's see an example:
[import ../example/doc_positional_insertion.cpp]
[doc_positional_insertion]
[endsect]
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()); }
//! <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`.
//!
//! <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); }
//! <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`.
//!
//! <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())); }
//! <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`.
//!
//! <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);
//! <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`.
//!
//! <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);
//! <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`.
//!
//! <b>Effects</b>: Finds a range containing all elements whose key is k or