How to clone a hook with Boost Intrusive?

380 views Asked by At

I'm learning Boost Intrusive library. I have a problem when I try to copy a STL container. I use a std::vector. It contains elements of class list_base_hook in the mode auto_unlink but the information about the node (is_linked()) is lost when you call the copy constructor.

I have the following code:

 class helper_class
 {
     public:
         helper_class(void) { /* ... */ }
         helper_class(const helper_class& hc) { /* ... */ }
         helper_class(helper_class&& hc) { /* ... */ }
         helper_class & operator=(const helper_class& hc) { /* ... */ }
         helper_class & operator=(helper_class&& hc) { /* ... */ }
         virtual ~helper_class(void) { /* ... */ }
         // ...
 };

 typedef list_base_hook<link_mode<auto_unlink> > auto_hook;

 class my_class : public auto_hook
 {
     public:

         friend bool operator==(const my_class &a, const my_class &b)
         {
             return (a.int_ == b.int_) &&
                     (a.helper_class_ == b.helper_class_);
         }

         int int_;
         helper_class* helper_class_;

         // ...
 };

 typedef list<my_class, constant_time_size<false> >  my_class_list;

 struct new_cloner
 {
      my_class *operator()(const my_class &clone_this)
      {  return new my_class(clone_this);  }
 };

 struct delete_disposer
 {
      void operator()(my_class *delete_this)
      {  delete delete_this;  }
 };

 int main()
 {
     // ...
     helper_class the_helper_class;
     const int MaxElem = 100;
     std::vector<my_class> nodes(MaxElem);
     std::vector<my_class> copy_nodes(MaxElem);
     my_class_list list;

     for(int i = 0; i < MaxElem; ++i) {
         nodes[i].int_ = i;
         nodes[i].helper_class_ = &the_helper_class;
     }

     list.insert(list.end(), nodes.begin(), nodes.end());

     my_class_list cloned_list;
     cloned_list.clone_from(list, new_cloner(), delete_disposer());

     copy_nodes = nodes;

     std::cout  << "nodes[0].is_linked()       : "
                << ((nodes[0].is_linked()) ? "LINKED":"NO-LINKED")
                << std::endl;
     std::cout  << "copy_nodes[0].is_linked()  : "
                << ((copy_nodes[0].is_linked()) ? "LINKED":"NO-LINKED")
                << std::endl;
     std::cout  << "list[0].is_linked()        : "
                << (((*list.begin()).is_linked()) ? "LINKED":"NO-LINKED")
                << std::endl;
     std::cout  << "cloned_list[0].is_linked() : "
                << (((*cloned_list.begin()).is_linked()) ? "LINKED":"NO-LINKED")
                << std::endl;
     cloned_list.clear_and_dispose(delete_disposer());

     // ...

     return 0;
 };

Standard output:

nodes[0].is_linked()       : LINKED
copy_nodes[0].is_linked()  : NO-LINKED
list[0].is_linked()        : LINKED
cloned_list[0].is_linked() : LINKED

Why the vector copy_nodes isn't linked?

Thanks you.

1

There are 1 answers

4
sehe On BEST ANSWER

Why would you expect a copied node to be in a collection?

If you print a book twice, do you expect it to be magically end up in the same library as the other book that was printed months ago?

It's just a different object. Also known as a copy.

If your copy would "magically" clone the hook as well, that would either break container invariants, or raise the question /where/ the copy should be inserted in the container.

After some serious debating, I figured you might want to know how to clone the list along with the values in a vector:

my_class_list cloned_list;
std::vector<my_class> cloned_nodes;
cloned_nodes.reserve(MaxElem);
cloned_list.clone_from(
        list, 
        [&cloned_nodes](my_class const&v) { cloned_nodes.push_back(v); return &cloned_nodes.back(); },
        [](my_class*){}
    );

There's no delete here (because you can just destroy the vector anyway). Here's a full demo of this

Live On Coliru

#include <boost/intrusive/list.hpp>
using namespace boost::intrusive;

struct my_class : list_base_hook<link_mode<auto_unlink> > { };
typedef list<my_class, constant_time_size<false> > my_class_list;

#include <iostream>

int main()
{
    const int MaxElem = 100;
    std::vector<my_class> nodes(MaxElem);

    //////////////////////////////////////////////
    // He's making a list
    my_class_list list;
    list.insert(list.end(), nodes.begin(), nodes.end());

    //////////////////////////////////////////////
    // He's checking it twice
    my_class_list cloned_list;
    std::vector<my_class> cloned_nodes;
    cloned_nodes.reserve(MaxElem);
    cloned_list.clone_from(
            list, 
            [&cloned_nodes](my_class const&v) { cloned_nodes.push_back(v); return &cloned_nodes.back(); },
            [](my_class*){}
        );

    std::cout << std::boolalpha;
    std::cout << "nodes[0].is_linked()       : " << nodes[0].is_linked()             << std::endl;
    std::cout << "cloned_nodes[0].is_linked(): " << cloned_nodes[0].is_linked()      << std::endl;
    std::cout << "list[0].is_linked()        : " << list.begin()->is_linked()        << std::endl;
    std::cout << "cloned_list[0].is_linked() : " << cloned_list.begin()->is_linked() << std::endl;

    //////////////////////////////////////////////
    // Gonna find out who's naughty or nice:
    auto nit = cloned_nodes.begin();
    auto lit = cloned_list.begin();

    while (nit != cloned_nodes.end() && lit != cloned_list.end()) {
        assert(&(*nit++) == &(*lit++)); // this would fail if you didn't `reserve()` the vector up front
    }

    //////////////////////////////////////////////
    // now, if you really want you can do
    cloned_list.clear();
    // after which the simplest thing to do would be `cloned_nodes.clear()`, but let's be very precise:
    cloned_nodes.erase(std::remove_if(
                cloned_nodes.begin(), cloned_nodes.end(), 
                [](my_class const& v) { return !v.is_linked(); }),
            cloned_nodes.end());
}

In fact, here's a version that puts the cloned nodes right there in the same vector as the source nodes, for fun: Live On Coliru too.