0

I need an intrusive, sorted, double-linked list. I do not want to use boost::intrusive, so I'm doing this myself and running into an issue

For a doubly-linked list there are several operations, here is just one of them which will help to illustrate my point:

template<typename LIST, typename NODE>
void insertAfter(LIST *list, NODE *node, NODE *newNode)
{
    newNode->prev_ = node;
    newNode->next_ = node->next_;
    if(nullptr == node->next_)
        list->last_ = newNode;
    else
        node->next_->prev_ = newNode;
    node->next_ = newNode;
}

Now suppose I have a set of objects that are in one such list BUT I want their guts private:

struct Object
{
private:
    Object *prev_, *next_;
};

Now I create my List (please ignore the fact that when the list is empty there will be a nullptr exception...).

struct List
{
    Object *first_, *last_;

    void addObject(Object *o)
    {
        insertAfter(this, last_, o);  // not correct when list empty
    }
};

This will not compile because prev_ and next_ are private and insertAfter does not have access. It can be solved easily with this:

// Fwd decl
struct List;

struct Object
{
    friend void insertAfter<List, Object>(List *, Object *, Object *);
private:
    Object *prev_, *next_;
};

struct List
{
    Object *first_, *last_;

    void addObject(Object *o)
    {
        insertAfter(this, last_, o);
    }
};

But this opens an access hole such that anyone can use insertAfter to affect Object's private members. What I really want is for List to be a friend of Object. I could solve this by not using templates for my linked list operation (use plain macros instead) but this obviously has its downside. What's the right way to go here?

Badmanchild
  • 990
  • 9
  • 18
  • Why not just use `std::list` (http://www.cplusplus.com/reference/list/list/) – Sean Jan 24 '14 at 12:05
  • 2
    `I do not want to use boost::intrusive, so I'm doing this myself` Can you expand on this? – Lightness Races in Orbit Jan 24 '14 at 12:10
  • @LightnessRacesinOrbit, what I'm really aiming for is an always sorted doubly linked list. By "always sorted" I mean that the insert() method is now O(n) and will use a comparator to place your item in the list in the sorted position. And I need this with intrusiveness... I looked at boost intrusive list: http://www.boost.org/doc/libs/1_55_0/doc/html/intrusive/list.html and it does not satisfy the sorting requirement. – Badmanchild Jan 24 '14 at 16:57
  • @Badmanchild: Why's that? Where do these requirements come from? (hint: these are implementation decisions, not requirements) Why don't you use a [multi]set instead? – Lightness Races in Orbit Jan 24 '14 at 16:58
  • @LightnessRacesinOrbit, sets are going to use a btree (or RB tree) and so will have O(log N), which is better -- one caveat I did not mention is that I will mostly be inserting near the low end of the list (i.e., lowest by key value) and so this is a worst case usage pattern for the set I think right? Perhaps I am wrong on this. – Badmanchild Jan 24 '14 at 17:01
  • @Badmanchild: Dunno. Look into it. Note that you can insert with an iterator hint that'll help _a lot_, though you may still incur excess rebalancing if that's your concern. – Lightness Races in Orbit Jan 24 '14 at 17:04

2 Answers2

1

How about something along these lines?

template<class ObjectType>
class List
{
    ObjectType *first_, *last_;

public:
    void addObject(ObjectType *o)
    {
        insertAfter(this, last_, o);
    }

    void insertAfter(ObjectType *node, ObjectType *newNode)
    {
        newNode->prev_ = node;
        newNode->next_ = node->next_;
        if(nullptr == node->next_)
            this->last_ = newNode;
        else
            node->next_->prev_ = newNode;
        node->next_ = newNode;
    }
};

class Object
{
private:
    Object *prev_, *next_;

    friend class List<Object>;
};

int main() {}

I can't see how it's really less offensive than what you've been doing already, though: template code is inline anyway, so you can't stop people from rewriting your class to their liking. Just relax and have reasonable degree of trust in your clients :)

Alexei Averchenko
  • 1,706
  • 1
  • 16
  • 29
0

You may do encapsulate the intrusive data, something like:

template <typename Object>
class List
{
public:
    class PrivateData
    {
        friend class List;
    public: // minimal stuff which should be public go here
        PrivateData() : prev_(nullptr), next_(nullptr) {}
    private:
        // other stuff
        Object *prev_, *next_;
    };

    // Other stuff

};

And then in your Object:

class Object
{
public:
    List<Object>::privateData listData_;
private:
    // internal data.
};

As all things in ListData are private, user can't do anything with listData_ but can be accessed by List which is friend.

Jarod42
  • 203,559
  • 14
  • 181
  • 302