230 likes | 446 Views
Simple Containers in C++. List: simple container class Iterators Memory Allocation Responsibility Mixed type collections Abstract Container Classes Non-Intrusive lists. Iterators.
E N D
Simple Containers in C++ • List: simple container class • Iterators • Memory Allocation Responsibility • Mixed type collections • Abstract Container Classes • Non-Intrusive lists
Iterators • An iterator is used for iterating (traversing/scanning) over all members of a container. Required operations are: • Declare • Initialize • Get element • Increment (Decrement?) • Check if last • Array iterators are just integers... • const N = 100; • double a[N]; • for (int i = 0; i < N; i++) { • ... // Process a[i] • ... • } i
List Container Class • Generalization: array with integer iterators - into a generalized list container with typical list operations, which could be used to store objects of any type. • We would like our container class used as follows: void driver(void) { List mid_east; mid_east.insert(new Leader("Assad", "Syria")); mid_east.insert(new Leader("Peres", "Israel")); mid_east.insert(new Leader("Hussein", "Jordan")); List::Iteratori(mid_east); for (; i; ((Leader *)i.next())->print()) ; mid_east.insert(new Leader("Mubarak", "Egypt")); for (i.reset(); i; ((Leader *)i.next())->print()) ; }
Memory Allocation Responsibilities • What’s stored in the container? • objects • Container must make a copy of all stored objects and be responsible for allocating and deallocating them. • pointers • Who allocates memory for the stored objects? • User - Automatic objects cannot be stored. • Container - Stored objects are created using copy constructor. • Who deallocates the objects? • User - The user code must keep record of all objects stored in the container, so that it could erase them. • Container - Usually the preferred way. Node::Node(const String& s) : str(s){} Node::Node(const String* s) : sptr(s){} Node::Node(const String& s) : sptr(new String(s)){}
Memory Allocation and Deallocation Responsibilities with Our Interface void driver(void) { List mid_east; mid_east.insert(new Leader("Assad", "Syria")); mid_east.insert(new Leader("Peres", "Israel")); mid_east.insert(new Leader("Hussein", "Jordan")); List::Iteratori(mid_east); for (; i; ((Leader *)i.next())->print()) ; mid_east.insert(new Leader("Mubarak", "Egypt")); for (i.reset(); i; ((Leader *)i.next())->print()) ; } Allocation in user code The container stores pointers to objects allocated in user code. Deallocation in container code The List destructor mid_east.~List executed when driver() returns must delete all the inserted items
The List Interface Nested type definition are used mainly for scope definition and for minimizing the pollution of the global name space. • class List { • public: • class Node { • ... • }; • class Iterator { • ... • }; • void insert(Node *object) { • object->next = first.next; • first.next = object; • } • .... • private: • Node first; // Dummy record • }; Indeed, the Ansi-C++ committee decided to add a namespace keyword to C++.
List Iterators ... List::Iteratori(mid_east); for (; i; ((Leader *)i.next())->print()) ; ... for (i.reset(); i; ((Leader *)i.next())->print()) ; • Declare and initialize - List::iterator i(mid_east) • Initialize - i.reset() • Get element - Value of i.next() • Increment - i.next() • Check if last - Cast to int. If 0 then list exhausted. Is next a mutator or an observer?
Inheritance and Heterogeneous Containers • The List::Node data type is nothing but its identity and linkage information. • The List data type may contain any type derived from List::Node. • class List { • ... • class Node { • Node *next; • Node(Node *n): next(n) {}friendclass List; • protected: • Node(void): next(NULL) {} • virtual ~Node(void) { delete next; } • }; • ... • }; Note that deleteNULLis perfectly legal in C++! Recursive deletion is not terribly efficient...
Inheriting From List::Node #include <iostream.h> class Leader: public List::Node { String name; String country; public: Leader(const char *n, constchar *c): name(n), country(c) {} void print(void) { cout << name << ';' << country << '\n'; } }; • Leader’s constructor will invoke List::Node’s default constructor • Leader’s destructor will invoke List::Node’s destructor
The Type Hierarchy so Far List List::Node List::Iterator • The nested class definitions are done only for minimizing pollution of the global name space Leader
New & Delete • class List { • ... • class Node { • ... • protected: • virtual ~Node(void) { ... } • public: • void *operatornew(size_t size) { • return ::operator new(size); • void operator delete(void *p, size_t size) { • ::operator delete(p); • }; ... • }; Always static, even if not declared as such! Employee::new for all classes derived from Employee The size parameter is that of the actual object allocated/deallocated.
Protected Data Members • Derived classes cannot access private parts (Or else it would have been a breach of privacy) • Protected data and methods can be accessed only by: • derived classes • members • friends
Protected Constructors • class List { • public: • class Iterator; • class Node { • friendclass List; • friendclass Iterator; • Node *next; • Node(Node *n): next(n) {} • protected: • Node(void): next(0) {} • virtual ~Node(void) { delete next; } • }; • ... • }; • The next field and the non-default constructor can be used by List and List::Iterator only. • Leader can only use the default constructor of List::Node.
Node Leader Node Leader Node Leader List Destruction and Virtual Destructors • Destruction sequence • List goes out of scope • טList destructed • טFirst node destructed • ט Other nodes destructed recursively • ~List::Node must be virtual to guarantee that ~Leader is called when the list is destructed List • class List { • ... • class Node { • ... • protected: • virtual ~Node(void) { delete next; } • }; • Node first; • };
Iterator Implementation • class List { • ... • class • public: • classIterator { • Node *first, *curr; • public: • Iterator(List& l) { first = &l.first; reset(); } • void reset(void) { curr = first->next; } • operatorint(void) { returncurr != NULL; } • Node *next(void) { • Node* tmp = curr; • if (curr != NULL) curr = curr->next; • returntmp; • } • }; • ... • Node first; // Dummy node used as first list element • };
Summary: List Interface • class List { • public: • class Node { • ... • }; • class Iterator { • ... • }; • void insert(Node *object) { • object->next = first.next; • first.next = object; • } • }; • List stores any class derived from List::Node. • Exported operations are insert and iteration Only. • Constructor and destructor are generated automatically.
Quiz void driver(void){ List mid_east; mid_east.insert(new Leader("Assad","Syria"));mid_east.insert(new Leader("Peres","Israel"));mid_east.insert(new Leader("Hussein","Jordan")); List::Iteratori(mid_east); for (; i; ((Leader *)i.next())->print()) ; mid_east.insert(new Leader("Mubarak","Egypt")); for (i.reset(); i; ((Leader *)i.next())->print()) ;} • What will be printed? • Describe the allocation and deallocation activities. • How is the following an indication of a design problem? • ((Leader *)i.next())->print()
Operator Overloading & Iterators • class List { • ... • classIterator { • Node *first, *curr; • Node *next(void) { • Node* tmp = curr; • if (curr != NULL) curr = curr->next; • returntmp; • } • public: • Iterator(List& l) { first = curr = l.first.next; } • void reset(void) { curr = first; } • operator int(void) { returncurr != NULL; } • Node * operator()(void){ returncurr; } • Iterator& operator++(void){ next(); return *this; } • }; • ... • }; • ... • for (List::Iteratori(mid_east); i; ++i) • cout << *(Leader *)i(); Why prefix operator++? Assumption: operator<< is overloaded for class Leader
Iteration Functions • Can be used for implementing general purpose iteration and search • voidForEach( List& list, • void(*func)(List::Node *current)) • { • for (List::Iterator i(l); i; ++i) func(i()); • } • List::Node *FirstThat(List& list, • int(*func)(List::Node *current)) • { • List::Node *current; • for (List::Iterator i(l); i; ++i) • if (func(current = i())) • return current; • return (List::Node *)0; • }
Using Iteration Functions • void Print(List::Node *n) • { • cout << (Leader *)n; • } • ... • ForEach(mid_east,Print); • ... • Leader Who, *found; • ... • intSearch(List::Node *n) • { • return (Leader *)n == Who; • } • ... • if ((found = FirstThat(mid_east,Search)) != NULL) • ...
Memory Allocation Responsibilities:Summary and Typical Errors • Better to store pointers in a container • Better let the container own the elements: • Elements destroyed when container destroyed. • Typical errors are: • Store an automatic object • Store a global/static object • Store the same object in more than one container. • Destroying explicitly an element in the container.