Hi guys,
I have to build a GRbTree class ( that is, a red_black binary tree with a graphical interface, maybe with Gtkmm ).
At this moment I have already done some other binary tree classes I'd like to reuse, then here is my current simple hierarchy:
- SimpleTree ( this is a simple, concrete implementation of a binary tree )
- RbTree ( this is derived from SimpleTree, it's a red_black self-balanced tree )
For these to work there's also need of some Node classes ( with members like left, right etc. ). Since RbTree must use some of the public and protected methods of SimpleTree, which require the argument to be a SimpleNode, the node used by RbTree ( RbNode ) must also be a SimpleNode; in other words RbNode must be public derived from SimpleNode :
- SimpleNode ( this is the node used by SimpleTree )
- RbNode ( this is derived from SimpleNode, adding up the notion of color )
Well, now here is how, in this way, the Node classes would look like :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
class SimpleNode {
public:
/* ... */
virtual SimpleNode& get_left() const { /* ... */ }
virtual SimpleNode& get_right() const { /* ... */ }
virtual SimpleNode& get_parent() const { /* ... */ }
virtual void set_left(SimpleNode& lc) { /* ... */ }
virtual void set_right(SimpleNode& rc) { /* ... */ }
virtual void set_parent(SimpleNode& father) { /* ... */ }
private:
SimpleNode* parent;
SimpleNode* left;
SimpleNode* right;
some_type* data;
int index;
};
class RbNode : public SimpleNode {
public:
/* ... */
virtual RbNode& get_left() const
{ return static_cast<RbNode& >(SimpleNode::get_left()); }
virtual RbNode& get_right() const
{ return static_cast<RbNode& >(SimpleNode::get_right()); }
virtual RbNode& get_parent() const
{ return static_cast<RbNode& >(SimpleNode::get_parent()); }
virtual void set_left(SimpleNode& lc)
{ SimpleNode::set_left(dynamic_cast<RbNode& >(lc)); }
virtual void set_right(SimpleNode& rc)
{ SimpleNode::set_right(dynamic_cast<RbNode& >(rc)); }
virtual void set_parent(SimpleNode& father)
{ SimpleNode::set_parent(dynamic_cast<RbNode& >(father)); }
virtual void set_left(RbNode& lc)
{ SimpleNode::set_left(lc); }
virtual void set_right(RbNode& rc)
{ SimpleNode::set_right(rc); }
virtual void set_parent(RbNode& father)
{ SimpleNode::set_parent(father); }
private:
NodeColor color;
};
|
As you can see, in the derived class there are 2 collections of set_something functions. The first one is overriding the set inherited from the base class and checking for bad arguments by dynamic_cast<> since is not possible to set a SimpleNode as parent or whatever of a RbNode.
Also, the get_something() methods need a static_cast<>, since nodes are stored in the base class as pointers to SimpleNodes, but surely the true types are RbNodes ( a tree is build up with the same type of nodes ).
Here is how the Tree classes would look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
class SimpleTree {
public:
/* ... */
protected:
virtual SimpleNode* make_node()
{ return new SimpleNode; }
/* ... */
virtual void set_root(SimpleNode& nroot)
{ root = &nroot; }
virtual void set_node(SimpleNode& node)
{ nodes[node.get_index()] = &node; }
private:
/* ... */
SimpleNode* root;
std::vector<SimpleNode*> nodes;
};
class RbTree : public SimpleTree {
public:
/* ... */
protected:
virtual RbNode<T>* make_node()
{ return new RbNode<T>; }
/* ... */
virtual void set_root(SimpleNode<T1>& nroot)
{ root = &dynamic_cast<RbNode<T1>& >(nroot); }
virtual void set_root(RbNode<T1>& nroot)
{ root = &nroot; }
virtual void set_node(SimpleNode<T1>& node)
{ nodes[node.get_index()] = &dynamic_cast<RbNode<T1>& >(node); }
virtual void set_node(SimpleNode<T1>& node)
{ nodes[node.get_index()] = &node; }
/* ... */
private:
/* ... */
};
|
Here we have the same as before, 2 collections of set_something() functions for the previously exposed reasons.
The make_node() method is something like a "virtual constructor" and I'll make an example on how it's used in my case:
Let RbTree have a insert() method, let also SimpleTree have a insert() method, as you know the first step of the insertion of a node in a RbTree is done in the same way as in a SimpleTree, then RbTree::insert() will first call SimpleTree::insert() and then do something else.
In this way SimpleTree::insert() will create a node and attach it somewhere on the tree, but, as told before, "a tree is build up with the same type of nodes" and hence insert() is designed to call the virtual method make_node() which will create the right type of node ( RbNode in this case ) instead of make something like "new SimpleNode".
This workaround works quite well while handling 2 classes, but it becomes critical with 3+ classes, check it out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
class GRbNode : public RbNode, public Gtk::DrawingArea {
public:
/* ... */
virtual GRbNode& get_left() const
{ return static_cast<GRbNode& >(RbNode::get_left()); }
virtual GRbNode& get_right() const
{ return static_cast<GRbNode& >(RbNode::get_right()); }
virtual GRbNode& get_parent() const
{ return static_cast<GRbNode& >(RbNode::get_parent()); }
virtual void set_left(SimpleNode& lc)
{ RbNode::set_left(dynamic_cast<GRbNode& >(lc)); }
virtual void set_right(SimpleNode& rc)
{ RbNode::set_right(dynamic_cast<GRbNode& >(rc)); }
virtual void set_parent(SimpleNode& father)
{ RbNode::set_parent(dynamic_cast<GRbNode& >(father)); }
virtual void set_left(RbNode& lc)
{ RbNode::set_left(dynamic_cast<GRbNode& >(lc)); }
virtual void set_right(RbNode& rc)
{ RbNode::set_right(dynamic_cast<GRbNode& >(rc)); }
virtual void set_parent(RbNode& father)
{ RbNode::set_parent(dynamic_cast<GRbNode& >(father)); }
virtual void set_left(GRbNode& lc)
{ RbNode::set_left(lc); }
virtual void set_right(GRbNode& rc)
{ RbNode::set_right(rc); }
virtual void set_parent(GRbNode& father)
{ RbNode::set_parent(father); }
private:
/* ... */
}
|
This is painful, 3 collections of set_something() functions !
For this reason I decided to seek out another solution, this is the idea: there is a base abstract class BaseTree which provide the common interface, all the Tree classes are derived from BaseTree and there's not anymore the Node hierarchy.
This would be the BaseTree class :
1 2 3 4 5 6 7 8 9 10
|
class BaseTree {
public:
virtual int search(const some_type&) =0;
virtual const some_type& get_data(int) =0;
virtual void destroy() =0;
virtual bool is_empty() =0;
virtual int store(const some_type&) =0;
virtual void remove(int) =0;
virtual void print() =0;
};
|
Then I wouldn't require whatever_cast<> anymore, since all the 3 classes are independent each other, but, instead, I wouldn't be able, for example, to use the SimpleTree::insert() method from RbTree, meaning that I will need to copy-paste the insert() method from SimpleTree to RbTree ( at most changing its argument from SimpleTree to RbTree ), with some waste of memory.
I'm asking:
What of these approach is better ?
Is there another way to solve this problem ?
Thanks.
EDIT : typo.