We now introduce a useful example that puts to use all of the material on object oriented design that we have discussed so far. We will build a general graph class in this chapter that was sketched out by Joh Lakos in his book on large scale software design (Lakos, 19). We will go through this class carefully here and then modify it to allow for graphical display in the X Window system in Chapter 4 and introduce a graphical interface tool based on this extension in Chapter 5. Finally, since we eventually wish to use these tools to allow very general types of computational objects to be organized in a graph structure of our choosing, we then extend the basic graph model to allow for the use of typeless data (implemented as the typdef Arbent void * in Chater 6. The typeless data graph model is an extension of ideas discussed in Wang's text on Object Oriented Programming (Wang, 31) and is another useful example of proper encapsulation techniques.
The graph class we implement here is built using an idea called levelization. If you think about it, graphs are inherently circular in nature: for example, how can you define nodes without referring to edges? To attempt to decouple these ideas, we design the graph class to separate the node and edge ideas as much as possible. We will use the following classes in our implementation:
Gnode *d_pointer_p;
GnodePtrBagLink *d_next_p;
or
Gedge *d_pointer_p;
GedgePtrBagLink *d_next_p;
void remove(GnodePtrBagLink **ADDR,GnodePtrBagLink *Key)
{
GnodePtrBagLink *tmp = Key; //this is the cell pointer we want to delete
Key = Key->next(); //reset to the next cell
//reset Bag
GnodePtrBagLink *Now = *ADDR; //set Now to head cell
GnodePtrBagLink *Prev;
if(tmp==Now){
//If the Key is at the head of the list, then
//by resetting *ADDR to the next cell, we
//effectively remove the head cell.
*ADDR = (*ADDR)->next();
//Once the reset is done, we can delete Now
delete Now;
}
else{
//In this case, the Key is not at the head, so we
//have to search through the list to find the cell
//whose next pointer is the Key.
Prev = Now;
Now = Now->next();
//If the Key was the Now from above, the following while
//loop would be skipped.
//
//If the Key was not the Now above, we enter the while
//loop to find the cell whose next point is the Key.
while( !(Now==tmp) ){
Prev = Now;
Now = Now->next();
}
//In either case, we arrive here with the cell information
//we need. We reset the Prev cell's next pointer to be
//the next pointer for Now (so we jump over the Key cell)
//and then we can delete Now.
Prev->d_next_p = Now->next();
delete Now;
}
}
The classes GedgePtrBagmanip and GnodePtrBagmanip
respectively will be implemented to allow us to do such removes.
We will use code like the remove() code above
in both the classes to implement the
GedgePtrBagmanip::remove() and GnodePtrBagmanip::remove()
method:
Let's look at these definition files in a skeleton form:
class Edge{
//overloaded I/O
friend istream& operator>>(istream& in, Edge& A);
friend istream& operator>>(istream& in, Edge* A);
friend ostream& operator<<(ostream& out,const Edge& A);
friend ostream& operator<<(ostream& out,const Edge* A);
private:
double d_weight;
public:
Edge();
Edge(double weight);
Edge(const Edge&);
~Edge();
Edge& operator=(const Edge&);
double weight() const;
istream& grab(istream& in);
ostream& print(ostream& out) const;
};
Note that by using overloaded I/O, we can use code like
cout << Edge;to print out the contents of an Edge object.
class Node{
// Overloaded I/O
friend istream& operator>>(istream& in, Node& A);
friend istream& operator>>(istream& in, Node* A);
friend ostream& operator<<(ostream& out,const Node& A);
friend ostream& operator<<(ostream& out,const Node* A);
private:
char *d_name_p;
public:
Node();
Node(const char *name);
Node(const Node&);
~Node();
Node& operator=(const Node&);
const char *name() const;
istream& grab(istream& in);
ostream& print(ostream& out) const;
};
class Gedge : public Edge{
friend Graph;
private:
Gnode *d_from_p;
Gnode *d_to_p;
Gedge(const Gedge&); //not implemented
Gedge& operator=(const Gedge&); //not implemented
Gedge(Gnode *from,Gnode *to,double weight);
~Gedge();
public:
Gnode *from();
Gnode *to();
};
class Gnode : public Node{
friend Graph;
private:
GedgePtrBag d_edges;
Gnode(const Gnode&); //not implemented
Gnode& operator=(const Gnode&); //not implemented
Gnode(const char *name);
~Gnode();
void add(Gedge *edgeptr);
void remove(Gedge *edgeptr);
public:
const GedgePtrBag& edges() const;
};
class Graph{
//Overloaded I/O
friend istream& operator>>(istream& in, Graph& A);
friend istream& operator>>(istream& in, Graph* A);
friend ostream& operator<<(ostream& out,const Graph& A);
friend ostream& operator<<(ostream& out,const Graph* A);
private:
GnodePtrBag d_nodes;
GedgePtrBag d_edges;
Graph(const Graph&);
Graph& operator=(const Graph&);
public:
Graph();
~Graph();
Gnode *addNode(const char *nodeName);
Gnode *findNode(const char *nodeName);
void removeNode(Gnode *node);
Gedge *addEdge(Gnode *from,Gnode *to,double weight);
Gedge *findEdge(Gnode *from,Gnode *to);
void removeEdge(Gedge *edge);
const GnodePtrBag& nodes() const;
const GedgePtrBag& edges() const;
istream& grab(istream& in);
ostream& print(ostream& out) const;
};
class GedgePtrBagLink{
friend GedgePtrBagIter;
friend GedgePtrBagManip;
private:
Gedge *d_pointer_p;
GedgePtrBagLink *d_next_p;
GedgePtrBagLink(const GedgePtrBagLink&);
GedgePtrBagLink& operator=(const GedgePtrBagLink&);
int operator==(const GedgePtrBagLink&);
public:
GedgePtrBagLink(Gedge *pointer,GedgePtrBagLink *next);
~GedgePtrBagLink();
GedgePtrBagLink *next() const;
Gedge *pointer() const;
};
class GedgePtrBag{
friend GedgePtrBagIter;
friend GedgePtrBagManip;
private:
GedgePtrBagLink *d_root_p;
GedgePtrBag(const GedgePtrBag&);
GedgePtrBag& operator=(const GedgePtrBag&);
public:
GedgePtrBag();
~GedgePtrBag();
void add(Gedge *pointer);
void removeAll(const Gedge *pointer);
};
class GedgePtrBagIter{
friend GedgePtrBagLink;
private:
GedgePtrBagLink *d_link_p;
GedgePtrBagIter(const GedgePtrBagIter& T);
GedgePtrBagIter& operator=(const GedgePtrBagIter&);
public:
GedgePtrBagIter(const GedgePtrBag& bag) : d_link_p(bag.d_root_p){};
~GedgePtrBagIter(){};
void operator++();
Gedge *operator()() const;
operator const void *() const;
};
void GedgePtrBagManip::advance()
{
*d_addrLink_p = (*d_addrLink_p)->next();
}
This code fails because *d_addrLink_p starts as the head cell address. Hence, a call to advance() will replace the head cell with the next cell. Also, this creates a possible memory leak as the replaced cell is never correctly deleted. The effect of this code fragment is that as we move through the bag calling the advance method to do so, we lose all the cells in the bag!! The correct way to do this is to use the local variable edgePtrBagLink *L to march through the bag:
void GedgePtrBagManip::advance()
{
L = L->next();
}
class GedgePtrBagManip{
private:
GedgePtrBagLink **d_addrLink_p;
GedgePtrBagLink *L;
GedgePtrBagManip(const GedgePtrBagManip&);
GedgePtrBagManip& operator=(const GedgePtrBagManip&);
public:
GedgePtrBagManip(GedgePtrBag* bag);
~GedgePtrBagManip();
void advance();
void remove();
Gedge *operator()() const;
operator const void *() const;
};