c++boostintrusive-containers

How to clone a hook with Boost Intrusive?


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.


Solution

  • 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.