Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Moving and Swapping Boost.Intrusive containers

No copy constructor
Move constructor
Move assignment
Swap

Boost.Intrusive containers have no copy constructor and no copy assignment. A value can be linked to only one container through each hook, and a container can not create new values, so a copy of a container can not contain the same values as the source. To copy a container, use clone_from (see Cloning Boost.Intrusive containers).

The move constructor links the nodes of the source container to the new container:

  • The new container gets all the values of the source container, in the same order. No value is copied, moved or destroyed: only the links change.
  • The predicates of the source container (comparison, priority, hash and equality function objects) are copied, not moved or swapped. The source container keeps valid predicates.
  • The value traits of the source container are moved and that operation must leave the source equal to the result (see Stateful value traits). The source container keeps valid value traits. Note: Before Boost 1.93 the behavior was not specified and it was not consistent: unordered containers moved the value traits, tree containers copied them, and list and slist swapped them in the move assignment. Now all containers move them, with the same requirement and with noexcept conditions that check these moves.
  • The source container is left empty and it can be used again.
  • Unordered containers: the new container takes the bucket array of the source container. The source container is left without a bucket array, so call rehash with a new bucket array before you insert values in it.
  • The move constructor is noexcept if the copies of the predicates and the moves of the value and bucket traits are noexcept.

From Boost 1.93, the move assignment a = boost::move(b) does these steps:

  • The values of a are unlinked, as with a.clear(). The values are not destroyed: the user still owns them.
  • The predicates of b are copied to a. b keeps its predicates.
  • The value traits of b are moved to a. b keeps value traits equal to the ones of a, as after a move construction.
  • The nodes of b are linked to a, in the same order.
  • b is left empty and it can be used again.
  • Unordered containers: a takes the bucket array of b and no longer uses its own bucket array. b is left without a bucket array, as after a move construction.
  • If a and b are the same object, there are no effects.
  • The move assignment is noexcept if the copies of the predicates and the moves of the value and bucket traits are noexcept.

This is the behavior of standard containers, where the previous elements of the target are destroyed and the source container does not receive them. An intrusive container does not own its values, so it unlinks them instead of destroying them. Other intrusive container libraries do the same: for example, the move assignment of LLVM's simple_ilist unlinks the values of the target and then splices the values of the source.

[Important] Important

Before Boost 1.93, the move assignment was equivalent to a.swap(b): b received the values, the predicates and the value traits of a. Code that uses the values of b after the move assignment must use swap explicitly.

For backwards compatibility, define the macro BOOST_INTRUSIVE_MOVE_ASSIGNMENT_SWAPS to get the behavior before Boost 1.93. Then the move assignment of all Boost.Intrusive containers is equivalent to swap and it is not noexcept. Define the macro before you include any Boost.Intrusive header, with the same value in all the translation units of the program (for example, in the build system), or the program violates the One Definition Rule. The macro does not change the containers of Boost.Container, which use Boost.Intrusive internally.

[Warning] Warning

BOOST_INTRUSIVE_MOVE_ASSIGNMENT_SWAPS is only a transition aid. It will be removed in a future release, after a transition period. Update the code that needs the old behavior to call swap explicitly.

#define BOOST_INTRUSIVE_MOVE_ASSIGNMENT_SWAPS
#include <boost/intrusive/list.hpp>

//...
a = boost::move(b);  //Same as a.swap(b): b now contains the values of a

a.swap(b) and swap(a, b) exchange the contents of two containers of the same type:

  • The values of a and b are exchanged. No value is copied, moved or destroyed: only the links change.
  • The predicates (comparison, priority, hash and equality function objects) are exchanged. They are swapped with an unqualified swap call: a swap function found with argument-dependent lookup, or otherwise the move constructor and the move assignment.
  • The value traits are exchanged in the same way. As for the moves of the value traits, their swap must not throw (see Stateful value traits).
  • Unordered containers: the bucket arrays are also exchanged, so each container keeps the bucket array that holds its nodes.
  • The complexity is constant. The only exception is slist without the linear<> or cache_last<> options: its swap is linear in the number of elements of both lists.

Exception guarantees:

  • list and slist: the swap does not throw.
  • Tree containers with one comparison object (set, multiset, rbtree, and the AVL, splay, scapegoat and binary search tree variants): the comparison objects are swapped first. If that swap throws and has no effects, the containers are not changed.
  • treap containers (comparison and priority) and unordered containers (hash and equality) swap two predicates, one after the other. The swaps of these predicates must not throw: if the second swap throws, the first predicates are already exchanged and the containers can not be used.

Iterators and references:

  • References and pointers to the values stay valid: they refer to the same values, now in the other container.
  • Iterators to the values stay valid and refer to the values in the other container, unless the value traits are stateful. Stateful value traits are exchanged too, and the iterators store a pointer to them, so the iterators are invalidated (see Stateful value traits).

Swap and move assignment are different operations from Boost 1.93: after a.swap(b), b contains the previous values of a. After a = boost::move(b), the previous values of a are unlinked and b is empty (see Move assignment). Use swap when both containers must keep their values.


PrevUpHomeNext