How to transfer nodes between two boost::intrusive::slist objects

320 Views Asked by At

Is it valid to transfer nodes between two boost::intrusive::slist<boost::intrusive::cache_last<true>> objects? Something like the following

auto one = boost::intrusive::slist<Node, boost::intrusive::cache_last<true>>{};
auto two = boost::intrusive::slist<Node, boost::intrusive::cache_last<true>>{};

auto node = std::make_unique<Node>();
one.push_back(*node);

auto& front = one.front();
one.pop_front();
two.push_back(front);

I'm getting a segmentation fault, and an assertion failure with boost version 1.70.0 https://wandbox.org/permlink/nWHakTYUiVBGKH6I. How can I fix this?


Note: I can't allocate a new node and copy the old one because I am using an intrusive list to have control over when and where the allocation happens.

2

There are 2 best solutions below

0
On

It seems to be the purpose of the splice method:

Effects: Transfers all the elements of list x to this list, before the the element pointed by it. No destructors or copy constructors are called.

two.splice(two.end(), one, one.begin());

Fully working demo:

#include <iostream>
#include <string>
#include <boost/intrusive/slist.hpp>

struct Node : public boost::intrusive::slist_base_hook<>
{
    int         i;
    std::string s;

    Node(int i, std::string s) : i(i), s(std::move(s)) {}
};

using NodeList = boost::intrusive::slist<Node, boost::intrusive::cache_last<true>>;

int main()
{
    NodeList one;
    NodeList two;

    auto show = [&](auto text){
        std::cout <<text <<"\n  one\n";
        for (auto & item : one) { std::cout <<"    " <<item.i <<' ' <<item.s <<'\n'; }
        std::cout <<"  two\n";
        for (auto & item : two) { std::cout <<"    " <<item.i <<' ' <<item.s <<'\n'; }
    };


    one.push_back(*new Node(42, "hello"));
    show("before splice");

    two.splice(two.end(), one, one.begin());
    show("after splice");

    one.clear_and_dispose([](Node * ptr){ delete ptr; });
    two.clear_and_dispose([](Node * ptr){ delete ptr; });
    return 0;
}

When run, will write:

before splice
  one
    42 hello
  two
after splice
  one
  two
    42 hello
0
On

You are deleting the node when it's still in a container -- the safe link mode checks for this and will assert.

If you delete the container first (reorder your object construction), or if you remove the node from the container before cleaning things up, there is no assertion.

e.g.

auto node = std::make_unique<Node>();
auto one = boost::intrusive::slist<Node, boost::intrusive::cache_last<true>>{};
auto two = boost::intrusive::slist<Node, boost::intrusive::cache_last<true>>{};

one.push_back(*node);

auto& front = one.front();
one.pop_front();
two.push_back(front);