c++templatesprivatefriendintrusive-containers

C++ How to avoid friend template functions with home-made intrusive lists


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?


Solution

  • 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 :)