Search code examples
c++structiteratorprivate

Implementing: private structs in Class


I have a List class which uses two objects: Element & Iterator

I considered making Element & Iterator classes but decided on structs.

V1. This works...

template <class T>
struct Element

template <class T>
struct Iterator



template <typename T>
class List {

public: 

typedef Iterator<T> iterator;


private:

};

Which allows a List iterator as you might expect:

List<int> list;
List<int>::iterator iter = list.begin();

V2. Another way is to declare the structs inside the class

template <typename T>
class List {

public:

    template <class T>
    struct Element

    template <class T>
    struct Iterator


private:


};

However having to include the second <> when creating iterator is a little less elegant & structs are public:

List<int> list;
List<int>::Iterator<int> iter = list.begin();

V3. Involves making the structs private, which is preferable:

template <class T>
class Iterator;


template <typename T>
class List {

public:


typedef Iterator<T> iterator;

private:

template <class T>
struct Element

template <class T>
struct Iterator

};

.

List<int> list;
List<int>::iterator iter list.begin();

Error 1 error C2079: 'begin' uses undefined class 'Iterator<T>'  main.cpp   216

Question:

How do I make structs private, allow public access to Iterator & keep the List::iterator syntax ?

eg List::iterator iter list.begin();

note: Iterator depends on Element

code:

#ifndef GUARD_List_h
#define GUARD_List_h

template <class T>
struct Element {

    Element() : prev(nullptr), next(nullptr), data(), t_flag(" ") {}

    Element<T>* prev;
    Element<T>* next;

    T data;
    int elem_ID;
    std::string t_flag;
};

template <class T>
struct Iterator {

    Iterator(Element<T>* e = nullptr) : elem(e) {}

    T& operator*(void) const {

        if (elem->t_flag == "sentinel"){ std::cerr << "No Element to De-Reference - End of List Reached"; }

        return elem->data;
    }

    T& operator++(void) {           // ++prefix

        elem = elem->next;
        return elem->data;
    }
    T operator++(const int) {               // postfix++

        elem = elem->next;
        return elem->prev->data;
    }
    T& operator--(const int) {          // --prefix

        elem = elem->prev;
        return elem->data;
    }
    T operator--(void) {            // postfix--

        elem = elem->prev;
        return elem->next->data;
    }

    Iterator<T>& operator+(const int val) {

        for (int i = 0; i < val; i++){

            elem = elem->next;
        }
        return *this;
    }
    Iterator<T>& operator-(const int val) {

        for (int i = 0; i < val; i++){

            elem = elem->prev;
        }
        return *this;
    }

    bool operator!=(const Iterator<T>& rhs) const {

        return elem->elem_ID != rhs.elem->elem_ID;
    }
    bool operator==(const Iterator<T>& rhs) const {

        return elem->elem_ID == rhs.elem->elem_ID;
    }
    bool operator>(const Iterator<T>& rhs) const {

        return elem->elem_ID > rhs.elem->elem_ID;
    }
    bool operator<(const Iterator<T>& rhs) const {

        return elem->elem_ID < rhs.elem->elem_ID;
    }
    bool operator>=(const Iterator<T>& rhs) const {

        return elem->elem_ID >= rhs.elem->elem_ID;
    }
    bool operator<=(const Iterator<T>& rhs) const {

        return elem->elem_ID <= rhs.elem->elem_ID;
    }


    Element<T>* elem;

};




template <typename T>
class List {

public:
    List() : sentinel(new Element<T>), Element_count(0)  {

        sentinel->t_flag = "sentinel";

        // double link: sentinel to itself
        sentinel->next = sentinel;
        sentinel->prev = sentinel;

    }
    virtual ~List()  {

        Element<T>* index = sentinel->next;
        Element<T>* index_next = sentinel->next->next;

        while (index->t_flag != "sentinel"){

            delete index;
            index = index_next;
            index_next = index_next->next;
        }
        delete sentinel;        
    }


    typedef Iterator<T> iterator;   


    Iterator<T> begin(void) const {

        Iterator<T> it(sentinel->next);
        return it;
    }
    Iterator<T> end(void) const {

        Iterator<T> it(sentinel);
        return it;
    }

    void push_back(const T val)  {

        Element<T>* elem = new Element<T>;      // create Element<T> object         
        elem->data = val;                       // set Element<T> data

        sentinel->prev->next = elem;            // link: end of List to Element object
        elem->prev = sentinel->prev;            // link: Element object to end of List   

        elem->next = sentinel;                  // link: new end of List to sentinel    
        sentinel->prev = elem;                  // link: sentinel to new end of List                

        elem->elem_ID = Element_count++;        // update: Element_count on grow        
    }
    T at(const size_t pos)  const {

        return get_Element(pos)->data;
    }
    void del(const size_t pos) const  {

        Element<T>* elem = get_Element(pos);    // get: Element for deletion        

        elem->prev->next = elem->next;          // rejoin: double link
        elem->next->prev = elem->prev;          // rejoin: double link

        delete elem;

        Element_count--;                        // update: Element_count on shrink

    }
    void clear(void) {

        Element<T>* index = sentinel->next;
        Element<T>* index_next = sentinel->next->next;

        while (index->t_flag != "sentinel"){

            delete index;
            index = index_next;
            index_next = index_next->next;
        }

        // double link: sentinel to itself
        sentinel->next = sentinel;
        sentinel->prev = sentinel;

        Element_count = 0;

    }

    size_t size(void) const {
        return Element_count;
    }
    bool empty(void) const {

        if (Element_count == 0){ return true; }
        else { return false; }
    }


private:



    Element<T>* sentinel;                         // List sentinel
    size_t Element_count;                         // List size

    Element<T>* get_Element(const size_t pos) const     {

        if (empty())                        {
            std::cerr << "No Element - Empty List";
            throw;          
        }
        if (pos < 0 || pos >= Element_count){
            std::cerr << "No Element - Out of Range";
            throw;          
        }


        Iterator<T> it;

        if ((Element_count / 2) > pos) {                        // Determine efficent direction ? 

            it = begin()+1;
            while ( it.elem->elem_ID != pos ){  it++;   }
        }
        else {

            it = end()-1;                           
            while ( it.elem->elem_ID != pos ){  it--;   }
        }

        return it.elem;
    }




};
#endif

Solution

  • As far as "elegance" is concerned, redundant typename declarations seem to be the biggest issue:

    If you have a nested class or struct, you don't need to templetize it; it is already bound to the templetized typename T declaration.

    So:

    template <typename T>
    class List {
    public:
        struct Element;
        struct Iterator;
    private:
    };
    

    Allows you to do:

    List<int> x;
    List<int>::iterator iter ( x.begin() );
    

    Since Iterator is being returned in a public method, it cannot be marked private. Element, on the other hand, is purely an implementation detail and can be marked private. You need to make sure that it is declared before any method that uses it or is simply forward declared.

    template<typename T>
    class List {
    private:
        struct Element; // or struct Element { <impl> };
    public:
        struct Iterator { < impl > };
    private:
    };