Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/container.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,15 @@ does not internally perform any value_type destruction, copy/move construction/a
corresponding to the insertion of new elements or deletion of stored elements, which can sometimes compensate in terms of
performance for the extra burden of doing more pointer manipulation and an additional allocation per element.

[*Splicing]. Since every element lives in its own node, [classref boost::container::stable_vector stable_vector] also offers
the three `splice` overloads of `std::list`: all the elements of another
[classref boost::container::stable_vector stable_vector], one of them, or a range of them, are transferred before a given
position by moving their node pointers from one index to the other, without copying, moving or destroying any element.
References, pointers and iterators to the transferred elements remain valid and refer to the same elements, so that
elements that are referred to by address can be passed from one container to another. The two containers must have
equal allocators, and the cost is linear in the number of transferred elements plus the number of elements that follow
the insertion point and the transferred range.

[*Exception safety]. (according to [@http://www.boost.org/community/exception_safety.html Abrahams' terminology])
As [classref boost::container::stable_vector stable_vector] does not internally copy/move elements around, some
operations provide stronger exception safety guarantees than in vector:
Expand Down Expand Up @@ -2883,6 +2892,9 @@ collect them containers and build [*Boost.Container], a library targeted to a wi

[section:release_notes_boost_1_93_00 Boost 1.93 Release]

* [classref boost::container::stable_vector stable_vector] gains the three `splice` overloads of `std::list`,
which transfer elements between two stable_vectors with equal allocators without copying, moving or destroying them.

* Containers now accept allocators whose \c value_type is \c void; such allocators
are rebound to the container's element type via \c real_allocator.

Expand Down
105 changes: 105 additions & 0 deletions include/boost/container/stable_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,111 @@ class stable_vector
return iterator(last.node_pointer());
}

//! <b>Requires</b>: p must be a valid iterator of *this, and x must be a
//! stable_vector other than *this whose allocator compares equal to the
//! one of *this.
//!
//! <b>Effects</b>: Transfers all the elements of x to *this, before p,
//! without copying, moving or destroying any of them; x is left empty.
//!
//! <b>Throws</b>: If the index of *this has to grow and its allocation
//! throws, in which case neither *this nor x change.
//!
//! <b>Complexity</b>: Linear to x.size() plus the number of elements of
//! *this from p to the end.
//!
//! <b>Note</b>: Iterators, pointers and references to the elements of x
//! remain valid, and refer to the same elements, which are now in *this.
void splice(const_iterator p, stable_vector &x)
{ this->splice(p, x, x.cbegin(), x.cend()); }

//! <b>Effects</b>: Same as splice(p, x), x being an rvalue.
void splice(const_iterator p, BOOST_RV_REF(stable_vector) x)
{ this->splice(p, static_cast<stable_vector &>(x)); }

//! <b>Requires</b>: p must be a valid iterator of *this, i a valid
//! dereferenceable iterator of x, and x a stable_vector other than *this
//! whose allocator compares equal to the one of *this.
//!
//! <b>Effects</b>: Transfers the element pointed by i from x to *this,
//! before p, without copying, moving or destroying it.
//!
//! <b>Throws</b>: If the index of *this has to grow and its allocation
//! throws, in which case neither *this nor x change.
//!
//! <b>Complexity</b>: Linear to the number of elements of *this from p to
//! the end plus the number of elements of x after i.
//!
//! <b>Note</b>: Iterators, pointers and references to the element remain
//! valid, and refer to the same element, which is now in *this.
void splice(const_iterator p, stable_vector &x, const_iterator i)
{
const_iterator n(i);
this->splice(p, x, i, ++n);
}

//! <b>Effects</b>: Same as splice(p, x, i), x being an rvalue.
void splice(const_iterator p, BOOST_RV_REF(stable_vector) x,
const_iterator i)
{ this->splice(p, static_cast<stable_vector &>(x), i); }

//! <b>Requires</b>: p must be a valid iterator of *this, [first, last) a
//! valid range of x, and x a stable_vector other than *this whose
//! allocator compares equal to the one of *this.
//!
//! <b>Effects</b>: Transfers the elements in [first, last) from x to *this,
//! before p, without copying, moving or destroying any of them.
//!
//! <b>Throws</b>: If the index of *this has to grow and its allocation
//! throws, in which case neither *this nor x change.
//!
//! <b>Complexity</b>: Linear to the distance between first and last plus
//! the number of elements of *this from p to the end plus the number of
//! elements of x from last to the end.
//!
//! <b>Note</b>: Iterators, pointers and references to the transferred
//! elements remain valid, and refer to the same elements, which are now
//! in *this.
void splice(const_iterator p, stable_vector &x,
const_iterator first, const_iterator last)
{
BOOST_ASSERT(this != &x);
BOOST_ASSERT(this->priv_node_alloc() == x.priv_node_alloc());
BOOST_ASSERT(this->priv_in_range_or_end(p));
BOOST_ASSERT(first == last ||
(first < last && x.priv_in_range(first) &&
x.priv_in_range_or_end(last)));
BOOST_CONTAINER_STABLE_VECTOR_CHECK_INVARIANT;
const size_type num = static_cast<size_type>(last - first);
if(!num){
return;
}
const size_type idx = static_cast<size_type>(p - this->cbegin());
const index_iterator x_first
(x.index.begin() + static_cast<difference_type>(first - x.cbegin()));
const index_iterator x_last(x_first + static_cast<difference_type>(num));
//Make room in the index of *this for the nodes of x, which is the only
//step that can throw, and fix up the pointers of the nodes that moved
index_traits_type::initialize_end_node
(this->index, this->internal_data.end_node, num);
const node_base_ptr_ptr old_buffer = this->index.data();
this->index.insert
(this->index.begin() + static_cast<difference_type>(idx),
x_first, x_last);
const index_iterator index_beg = this->index.begin();
index_traits_type::fix_up_pointers_from
(this->index, this->index.data() != old_buffer ?
index_beg : index_beg + static_cast<difference_type>(idx));
//Now the nodes belong to *this: drop them from the index of x
const index_iterator e = x.index.erase(x_first, x_last);
index_traits_type::fix_up_pointers_from(x.index, e);
}

//! <b>Effects</b>: Same as splice(p, x, first, last), x being an rvalue.
void splice(const_iterator p, BOOST_RV_REF(stable_vector) x,
const_iterator first, const_iterator last)
{ this->splice(p, static_cast<stable_vector &>(x), first, last); }

//! <b>Effects</b>: Swaps the contents of *this and x.
//!
//! <b>Throws</b>: Nothing.
Expand Down
91 changes: 91 additions & 0 deletions test/stable_vector_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,99 @@ bool test_unqualified_swap()
return true;
}

//Checks that sv holds the values of v, in order, through iterators and operator[]
bool test_splice_check(const stable_vector<test::non_copymovable_int> &sv,
const int *v, std::size_t n)
{
if(sv.size() != n)
return false;
std::size_t i = 0;
for( stable_vector<test::non_copymovable_int>::const_iterator it = sv.begin()
; it != sv.end(); ++it, ++i){
if(it->get_int() != v[i] || sv[i].get_int() != v[i])
return false;
}
return true;
}

bool test_splice()
{
typedef stable_vector<test::non_copymovable_int> cont;
cont a, b, c;
for(int i = 0; i < 5; ++i)
a.emplace_back(i); //0 1 2 3 4
for(int i = 10; i < 13; ++i)
b.emplace_back(i); //10 11 12
const unsigned int count = test::non_copymovable_int::count;
const test::non_copymovable_int *p11 = &b[1], *p2 = &a[2], *p4 = &a[4];

//All of b, in the middle of a
a.splice(a.cbegin() + 2, b);
{ const int v[] = { 0, 1, 10, 11, 12, 2, 3, 4 };
if(!test_splice_check(a, v, 8) || !b.empty())
return false;
}
if(&a[3] != p11 || &a[5] != p2 || &a[7] != p4)
return false;

//One element, into an empty stable_vector
c.splice(c.cend(), a, a.cbegin() + 3);
{ const int v[] = { 11 }, w[] = { 0, 1, 10, 12, 2, 3, 4 };
if(!test_splice_check(c, v, 1) || !test_splice_check(a, w, 7) || &c[0] != p11)
return false;
}

//A range at the end of a, at the beginning of c
c.splice(c.cbegin(), a, a.cbegin() + 4, a.cend());
{ const int v[] = { 2, 3, 4, 11 }, w[] = { 0, 1, 10, 12 };
if(!test_splice_check(c, v, 4) || !test_splice_check(a, w, 4))
return false;
}
if(&c[0] != p2 || &c[2] != p4)
return false;

//An empty range changes nothing
a.splice(a.cend(), c, c.cbegin(), c.cbegin());
{ const int v[] = { 2, 3, 4, 11 }, w[] = { 0, 1, 10, 12 };
if(!test_splice_check(c, v, 4) || !test_splice_check(a, w, 4))
return false;
}

//Splicing constructs and destroys nothing
if(test::non_copymovable_int::count != count)
return false;

//A growth of the index of the receiving stable_vector, and back
for(int i = 0; i < 1000; ++i)
b.emplace_back(100 + i);
const test::non_copymovable_int *p100 = &b[0], *p1099 = &b[999];
a.splice(a.cbegin() + 1, b);
if(a.size() != 1004 || !b.empty() || &a[1] != p100 || &a[1000] != p1099)
return false;
b.splice(b.cend(), a, a.cbegin() + 1, a.cbegin() + 1001);
{ const int w[] = { 0, 1, 10, 12 };
if(!test_splice_check(a, w, 4) || b.size() != 1000 || &b[0] != p100 || &b[999] != p1099)
return false;
}

//Both stable_vectors work as usual afterwards
a.erase(a.cbegin() + 2);
a.emplace(a.cbegin(), 7);
c.splice(c.cend(), boost::move(a));
{ const int v[] = { 2, 3, 4, 11, 7, 0, 1, 12 };
if(!test_splice_check(c, v, 8) || !a.empty())
return false;
}
return true;
}

int main()
{
if(!test_splice()){
std::cerr << "test_splice failed" << std::endl;
return 1;
}

if(!test_unqualified_swap())
return 1;

Expand Down