views:

258

answers:

2

Hi folks,

I'm interested in doing something like the following to adhere to a Null Object design pattern and to avoid prolific NULL tests:

class Node;
Node* NullNode;

class Node {
public:
  Node(Node *l=NullNode, Node *r=NullNode) : left(l), right(r) {};
private:
  Node *left, *right;
};

NullNode = new Node();

Of course, as written, NullNode has different memory locations before and after the Node class declaration. You could do this without the forward declaration, if you didn't want to have default arguments (i.e., remove Node *r=NullNode).

Another option would use some inheritence: make a parent class (Node) with two children (NullNode and FullNode). Then the node example above would be the code for FullNode and the NullNode in the code above would be of type NullNode inheriting from Node. I hate solving simple problems by appeals to inheritence.

So, the question is: how do you apply Null Object patterns to recursive data structures (classes) with default arguments (which are instances of that same class!) in C++?

+2  A: 

Use extern:

extern Node* NullNode;
...
Node* NullNode = new Node();

Better yet, make it a static member:

class Node {
public:
  static Node* Null;
  Node(Node *l=Null, Node *r=Null) : left(l), right(r) {};
private:
  Node *left, *right;
};

Node* Node::Null = new Node();

That said, in both existing code, and amendments above, you leak an instance of Node. You could use auto_ptr, but that would be dangerous because of uncertain order of destruction of globals and statics (a destructor of some global may need Node::Null, and it may or may not be already gone by then).

Pavel Minaev
Right. Absolutely, I knew I was forgetting one of the "name manipulators". I forgot about extern b/c I normally use it in a different context (i.e., a declaration external to the current file!). I think I'm leaning towards an inheritence based solution so that the NullNode can be made to "do nothing" (as opposed to test everywhere). This is sample code for students, so I'm balancing readability of the code (too many tests clutter things up) with readability of the class structure (inheritance may make eyes glaze over). Thanks!
Mark
Self-correction: order of initialization is a problem regardless of whether you use `auto_ptr` or not. If you absolutely need to get it right, you'll have to use the "phoenix singleton" pattern.
Pavel Minaev
A: 

Invert the hierarchy. Put the null node at the base:

class Node {
public:
  Node() {}
  virtual void visit() const {}
};

Then specialize as needed:

template<typename T>
class DataNode : public Node {
public:
  DataNode(T x, const Node* l=&Null, const Node* r=&Null)
    : left(l), right(r), data(x) {}

  virtual void visit() const {
    left->visit();
    std::cout << data << std::endl;
    right->visit();
  }

private:
  const Node *left, *right;
  T data;
  static const Node Null;
};

template<typename T>
const Node DataNode<T>::Null = Node();

Sample usage:

int main()
{
  DataNode<char> a('A', new DataNode<char>('B'),
                        new DataNode<char>('C'));

  a.visit();

  return 0;
}

Output:

$ ./node 
B
A
C
Greg Bacon
I came to a similar conclusion on the drive home yesterday. Any thoughts on cost/benefit to the Node->DataNode hierarchy versus Node->NullNode and Node->DataNode version? What might modifications might we make that would make one more flexible than the other?
Mark
With `NullNode` as the base, assuming `DataNode` is a template, you don't have to use an ugly dummy template-parameter as in `class NullNode : public DataNode<int> ...`, and this arrangement is also a better fit for the “is-a” relationship.
Greg Bacon