简体   繁体   中英

Range based for loop for linked list

I have a function which operates on a nested linked list. The function is the following:

void DoLiana(void) {

    PlotPointer plot;
    TreePointer tree;

        plot = FirstPlot;
        while (plot != nullptr) {
            tree = plot->FirstTree;
            while (tree != nullptr) {
                if (tree->isLiana) {
                    if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                    DoLianaAttachement(plot, tree);
                }
                tree = tree->next;
            }
            plot = plot->next;
        }    
}

Because this type of iterations happen multiple times in my code I am looking for a way to make the iteration more compact and expressive. I read that in C++11 there are ranged based for loops that iterate over a set. Would this construct be applicable in this situation? Or are there other possible ways to perform these iterations?

Yes you can define the appropriate functions for this.

Since oyu have given very few details. Lets make some assumptions.

struct Tree
{
    bool  isLiana;
    void* attachedTree;
    Tree* next;
};

using TreePointer = Tree*;

struct Plot
{
    TreePointer FirstTree;
    Plot*       next;
};

using PlotPointer = Plot*;

bool TestForLianaAttach(PlotPointer, TreePointer);
void DoLianaAttachement(PlotPointer, TreePointer);

PlotPointer FirstPlot;

To make this work with pointers you need to define the appropriate begin() and end() methods for your pointers.

NextIterator<Plot> begin(PlotPointer ptr)  {return make_NextIterator(ptr);}
NextIterator<Plot> end(PlotPointer)        {return make_NextIterator<Plot>();}

NextIterator<Tree> begin(TreePointer ptr)  {return make_NextIterator(ptr);}
NextIterator<Tree> end(TreePointer)        {return make_NextIterator<Tree>();}

The range based for looks for begin() and end() functions that can be used with your type. Now the standard has default std::begin() and std::end() that call the begin() and end() methods on the objects passed. But you can provide your own (like the above) to do a special case for your type/pointer.

Now since your pointers use p = p->next; to advance we need an iterator object that does this part of the work. In the above code I have called this NextIterator . It is relatively easy to define.

template<typename T>
struct NextIterator
{
    T* p;

    NextIterator():       p(nullptr) {}
    NextIterator(T* ptr): p(ptr)     {}

    NextIterator& operator++(){p = p->next;return *this;}

    T const& operator*() const  {return *p;}
    T&       operator*()        {return *p;}
    T const* operator->() const {return p;}
    T*       operator->()       {return p;}

    bool operator==(NextIterator const& rhs) const {return p == rhs.p;}
    bool operator!=(NextIterator const& rhs) const {return p != rhs.p;}
};
template<typename T>
NextIterator<T> make_NextIterator(T* val)   {return NextIterator<T>(val);}
template<typename T>
NextIterator<T> make_NextIterator()         {return NextIterator<T>{};}

Now we can re-write your loops using the range based for.

void DoLianaRange(void) {

        for(auto& plot: FirstPlot) {
            for(auto& tree: plot.FirstTree) {
                if (tree.isLiana) {
                    if (tree.attachedTree == nullptr && TestForLianaAttach(&plot, &tree))
                    DoLianaAttachement(&plot, &tree);
                }
            }
        }
}

Original version for comparison.

void DoLiana(void) {

    PlotPointer plot;
    TreePointer tree;

        plot = FirstPlot;
        while (plot != nullptr) {
            tree = plot->FirstTree;
            while (tree != nullptr) {
                if (tree->isLiana) {
                    if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                    DoLianaAttachement(plot, tree);
                }
                tree = tree->next;
            }
            plot = plot->next;
        }
}

Or you could simply use the standard for loop!!

void DoLianaForLoop(void) {

        for (PlotPointer plot = FirstPlot; plot != nullptr; plot = plot->next) {
            for (TreePointer tree= plot->FirstTree; tree != nullptr; tree = tree->next) {
                if (tree->isLiana) {
                    if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                    DoLianaAttachement(plot, tree);
                }
            }
        }
}

Code all in one place (in the correct order to compile).

struct Tree
{
    bool  isLiana;
    void* attachedTree;
    Tree* next;
};

using TreePointer = Tree*;

struct Plot
{
    TreePointer FirstTree;
    Plot*       next;
};

using PlotPointer = Plot*;

template<typename T>
struct NextIterator
{
    T* p;

    NextIterator():       p(nullptr) {}
    NextIterator(T* ptr): p(ptr)     {}

    NextIterator& operator++(){p = p->next;return *this;}

    T const& operator*() const  {return *p;}
    T&       operator*()        {return *p;}
    T const* operator->() const {return p;}
    T*       operator->()       {return p;}

    bool operator==(NextIterator const& rhs) const {return p == rhs.p;}
    bool operator!=(NextIterator const& rhs) const {return p != rhs.p;}
};

template<typename T>
NextIterator<T> make_NextIterator(T* val)   {return NextIterator<T>(val);}
template<typename T>
NextIterator<T> make_NextIterator()         {return NextIterator<T>{};}

NextIterator<Plot> begin(PlotPointer ptr)  {return make_NextIterator(ptr);}
NextIterator<Plot> end(PlotPointer)        {return make_NextIterator<Plot>();}

NextIterator<Tree> begin(TreePointer ptr)  {return make_NextIterator(ptr);}
NextIterator<Tree> end(TreePointer)        {return make_NextIterator<Tree>();}

bool TestForLianaAttach(PlotPointer, TreePointer);
void DoLianaAttachement(PlotPointer, TreePointer);

PlotPointer FirstPlot;

void DoLianaRange(void) {

        for(auto& plot: FirstPlot) {
            for(auto& tree: plot.FirstTree) {
                if (tree.isLiana) {
                    if (tree.attachedTree == nullptr && TestForLianaAttach(&plot, &tree))
                    DoLianaAttachement(&plot, &tree);
                }
            }
        }
}
void DoLiana(void) {

    PlotPointer plot;
    TreePointer tree;

        plot = FirstPlot;
        while (plot != nullptr) {
            tree = plot->FirstTree;
            while (tree != nullptr) {
                if (tree->isLiana) {
                    if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                    DoLianaAttachement(plot, tree);
                }
                tree = tree->next;
            }
            plot = plot->next;
        }
}

To follow up on Serge Ballesta's comment , you could immediately use vanilla for loops here, replacing the while loops. So your sample code will become:

void DoLiana(void) {

    for (PlotPointer plot = FirstPlot; plot; plot = plot->next) { 
        for (TreePointer tree = plot->FirstTree; tree; tree = tree->next) {
            if (tree->isLiana && !tree->attachedTree && TestForLianaAttach(plot, tree)) {
                DoLianaAttachement(plot, tree);
            }
        }
    }
}

This shortens the code, and adds locality and perhaps readability. And also maintains compatibility with C, if this is an advantage.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM