Boost logo

Boost :

From: Tobias Schwinger (tschwinger_at_[hidden])
Date: 2007-08-08 20:15:17


Ion Gaztañaga wrote:
> Tobias Schwinger wrote:
>> However, I've been wondering whether it would be possible to make the
>> "hook interface" easier to use and less demanding in terms of names that
>> are injected into client code. So I started experimenting a bit and the
>> attachment is what I came up with. I believe that scheme might actually
>> work (not sure, though)...
> >
>> [code]
> >
>
> Very interesting, although I don't fully understand the goal. If the
> goal is to avoid name injection, I could rework hooks to group all the
> name definitions in a single internal structure and avoid code bloat.

Maybe because there were two goals:

1. Get rid of the syntactic bloat currently needed to make a class
Intrusive-ready.
2. Reduce the probability of name clashes with arbitrary existing client
code.

Here is a second take of the code hopefully addressing the issues you
pointed me to.

> What I found interesting is the non-intrusive Intrusive version (nice
> definition ;-)). Using ADL, there is a way to obtain the hook from the
> value. However, Intrusive uses a two level architecture, where node
> algorithms only know about nodes (which in the singly linked list case
> is just an structure holding a single pointer) and containers know also
> about values (values hold nodes/hooks as base or members).

Well, it was a "waste product" trying to preserve flexibility, they
might as well be replaced with function objects, as you suggested below.

> This guarantees that template bloat is minimized, because instantiated
> algorithms are the same for all values using the same hook. Containers
> transform nodes to values and the inverse (using static_cast for base
> hooks or calculating the offset of the hook in the value in member hooks).

Ah, I see! So the "syntactic bloat" sorta just fell into place
minimizing the "machine language bloat" some misguided compilers' code
generation stages produce (not recognizing the that X<T> and X<U> are
equivalent for something like

      template<class T>
      class X
      {
          T* x;
          T* y;
        public:
          /* ...member functions that work on x and y ... */
      };

).

I noticed the two-level architecture, but didn't quite understand what
it's for. Now that I know, I see my sample introduced an undesirable
type dependency.

>
> Although your example is not directly applicable to current Intrusive
> code, you suggest a very nice possibility: storing nodes(hooks)
> independently from values. The programmer could just offer a way to get
> the node from the value and the inverse when not using any base or
> member hook.
>
> In the two-level Intrusive architecture your example would need two maps
> (we could just use boost::bimap) to transform independently stored nodes
> (slist_node_mgmt_data in your example) to values and the inverse operation.

... or extended the node structure with a back pointer like the attached
code. I didn't notice boost::bimap is checked-in already, until now.

<snip>

> A lot of questions, but I think it's a fresh, great idea. Thanks!

Glad you like it.

Regards,
Tobias


#include <boost/pointee.hpp>
#include <boost/noncopyable.hpp>

namespace intrusive
{
// ---- node with policies

    // management data for one node
    template< class Policies = void >
    class slist_node_mgmt_data
    {
        template< typename U, class Tr > friend class slist;
        slist_node_mgmt_data* ptr_next;

      public:
        slist_node_mgmt_data() { }
    };

    // ADL extension points to allow Boost.Intrusive to work with custom
    // members or even non-intrusively (using some external, bidirectional
    // mapping).
    // The default implementations assume the hook a base of the value.
    template< class Policies >
    slist_node_mgmt_data<Policies>& value_to_hook(
        slist_node_mgmt_data<Policies>& value)
    {
        return value;
    }

    // Note: Raw pointers will probably be enough, here...
    template< typename NodePointer, class Policies >
    void hook_to_value( NodePointer& result,
        slist_node_mgmt_data<Policies>& node)
    {
        // ... (using Smart Pointer - capable implementation just in case).
        // If not, Smart Pointers must provide support for boost::pointee
        // (e.g. an 'element_type' member).

        result = NodePointer( static_cast<
            typename boost::pointee<NodePointer>::type* >(& node) );
    }

// ---- container

    // traits for customization
    template< typename T >
    struct slist_node_traits
    {
        typedef T value_type;
        typedef T* pointer_type;
        typedef T& reference;

        // not sure it belongs here, move elsewhere at will
        typedef void policies;

        // ...
    };

    // single-linked list (incomplete for illustration)
    template< typename T, class Traits = slist_node_traits<T> >
    class slist : boost::noncopyable
    {
        typedef Traits traits;
      public:
        typedef typename traits::value_type value_type;
        typedef typename traits::pointer_type pointer_type;
        typedef typename traits::reference reference;
 
        typedef typename traits::policies policies;

        // ...

        slist_node_mgmt_data<policies>* ptr_first;
      public:

        // Note: Algorithms can work without depending on the value type
        // (except for casting or mapping for input and output, which will
        // always be there in some form).

        void push_front(reference x)
        {
            slist_node_mgmt_data<policies>& node = value_to_hook(x);
            node.ptr_next = this->ptr_first;
            this->ptr_first = & node;
        }

        void pop_front()
        {
            this->ptr_first = value_to_hook(* this->ptr_first).ptr_next;
        }

        reference front()
        {
            pointer_type result;
            hook_to_value(result, *this->ptr_first);
            return *result;
        }

        // ...
    };
}

#include <boost/detail/lightweight_test.hpp>
#include <map>

class foo
  : public intrusive::slist_node_mgmt_data<>
// Note: Base class does not insert any names
{
    int val_data;
  public:

    foo(int data)
      : val_data(data)
    { }

    int data() const
    { return this->val_data; }

    // Note: No "member hook"
};

void foo_test()
{
    intrusive::slist<foo> my_list;

    foo foo1(1); my_list.push_front(foo1);
    foo foo2(2); my_list.push_front(foo2);

    BOOST_TEST( my_list.front().data() == 2 );
    my_list.pop_front();
    BOOST_TEST( my_list.front().data() == 1 );
    my_list.pop_front();
}

// Ok, let's try non-intrusive Intrusive ;-)

class bar
// Note: No base class
{
    int val_data;
  public:

    bar(int data)
      : val_data(data)
    { }

    int data() const
    { return this->val_data; }

    // Note: No "member hook"
};

// External management facility

struct mgmt_data : intrusive::slist_node_mgmt_data<>
// adds a back pointer for bidi mapping
{
    bar* ptr_value;
};

std::map<bar*,mgmt_data> bar_mgmt_map;

intrusive::slist_node_mgmt_data<>& value_to_hook(bar& that)
{
    mgmt_data& result = bar_mgmt_map[& that];
    result.ptr_value = & that; // set back pointer
    return result;
}

void hook_to_value(bar*& result, intrusive::slist_node_mgmt_data<>& that)
{
    // downcast to access the back pointer
    result = static_cast<mgmt_data&>(that).ptr_value;
}

void bar_test()
{
    intrusive::slist<bar> my_list;

    bar bar1(1); my_list.push_front(bar1);
    bar bar2(2); my_list.push_front(bar2);

    BOOST_TEST( my_list.front().data() == 2 );
    my_list.pop_front();
    BOOST_TEST( my_list.front().data() == 1 );
    my_list.pop_front();
}

// Run the tests...

int main()
{
    foo_test();
    bar_test();

    return boost::report_errors();
}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk