If the goal here is to have two seperate types of nodes (Tnode and Rbtnode) that share a common base, one way to go would be to have the shared base
read-only. IE: 'get' functions can be polymorphed into derived types, but 'set' functions cannot be (at least not without killing type safety).
Breakdown:
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
|
template <typename T> class NodeBase
{
public:
NodeBase() : left(0), right(0), father(0), data(0), index(0) { }
virtual NodeBase<T>* get_left() = 0; // note: pure virtual
virtual NodeBase<T>* get_right() = 0;
virtual NodeBase<T>* get_father() = 0;
T& get_data() { return *data; }
// note: no 'set' methods for the nodes, but you can have set methods
// for data/index
void set_data(const T& d) { /*add code here*/ }
void set_index(int i) { index = i; }
protected:
NodeBase<T>* left;
NodeBase<T>* right;
NodeBase<T>* father;
T* data;
int index;
};
//-------------------------------------------
template <typename T> class Tnode
{
public:
// polymorph get functions
virtual Tnode<T>* get_left() { return static_cast< Tnode<T>* >(left); }
// same for get_right, get_father
// implement set functions
void set_left(TNode<T>* l) { left = l; }
// same for set_right, set_father
};
// do same for Rbtnode
| |
You can polymorph the 'get' functions for these to have derived classes return a derived type. IE: Tnode can return a Tnode* even though the original function is declared as NodeBase*. Note however that since (in this example) NodeBase contains 'NodeBase*' for nodes, Tnode's get functions need to downcast with static_cast (you could use dynamic_cast, too, but would be more expensive, and unnecessary if you guarantee only Tnode*s can be added to another Tnode.
--------------------------------------------
Set functions can be done in a similar manner, but you can't have a one-size-fits-all version in the base class. For example, consider the following situation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
template <typename T> class NodeBase
{
...
void set_left(NodeBase<T>* l) { left = l; }
};
void somefunc()
{
Tnode* tree = GetTreeFromSomewhere();
NodeBase* foo = tree; // upcast to generic base class
Rbnode* bar = GetRBNode();
foo->set_left( bar ); // no compiler error, 'bar' is indeed a NodeBase*
Tnode* nd = foo->get_left(); // no compiler error, but your program explodes! (bad cast)
}
| |
The above code (what it sounds like you're trying to accomplish) cannot be [easily] done for this very reason.
Because a generic base class 'set' implementation would accept any generic node, there's no type checks to ensure that the node is the right type. In this example we "successfully" added an Rbtnode to a Tnode tree -- so when Tnode's get_left function tries to cast the given NodeBase pointer to a Tnode pointer, you get [quietly] hosed. Very, very bad.
The alternative is to derive 'set' methods, but do type checking with dynamic_cast to ensure the right node is being added:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
template <typename T> class NodeBase
{
...
virtual void set_left(NodeBase<T>* l) = 0; // again, pure virtual
};
template <typename T> class Tnode
{
...
virtual void set_left(NodeBase<T>* l)
{
left = dynamic_cast< Tnode<T>* >(l); // MUST be dynamic_cast, static_cast is no good here
}
};
| |
If we try the foo/bar example thing with this setup, the dynamic_cast would fail, resulting in
left = 0;
. This might be bad if you expect the tree to clean up the nodes (this bad cast might lead to a memory leak if you don't check whether or not it was successful). So you might want to go with the "louder" version of dynamic_cast instead:
1 2 3 4
|
virtual void set_left(NodeBase<T>* l)
{
left = &(dynamic_cast< Tnode<T>& >(*l)); // dynamic_cast to a reference
}
| |
Same end result if the cast is successful, but here, if the cast is unsuccessful, instead of quietly returning null leading to a potential memory leak, dynamic_cast will throw an exception.
This will let you have both get/set functions available with just a parent 'NodeBase' class pointer. The downside is you need to polymorph all 6 get/set functions for each derived class (extra typing -- but not much of a performance hit). Plus as you can see, casting is still a necessity, however it can all remain inside the classes.
It's also worth mentioning that dynamic_cast requires runtime checks to ensure the type is really what you're casting it to, and therefore it's a little slower. Consider the following:
1 2 3 4
|
Rbtnode* tree = GetRBTree();
Rbtnode* foo = GetNode();
tree->set_left( foo ); // <--- unnecessary dynamic_cast, we already know the type is correct
| |
To work around that, you can have two versions of the set functions in your class. A virtual one which polymorphs the parent class (downcasts with dynamic_cast), and a non-virtual one which takes a specific type (no need to dynamic_cast):
1 2 3 4 5 6 7 8 9 10 11
|
// in class 'Rbtnode'
virtual void set_left(NodeBase<T>* l)
{
// do dynamic_cast here
}
void set_left(Rbtnode<T>* l)
{
left = l; // no need to dynamic_cast, type is already known to be legit
}
| |