Patrón de objeto nulo, clase recursiva y declaraciones directas
Pregunta
Estoy interesado en hacer algo como lo siguiente para adherirme a un patrón de diseño de Objetos Nulos y evitar prolíficas pruebas NULL:
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();
Por supuesto, como está escrito, NullNode tiene diferentes ubicaciones de memoria antes y después de la declaración de la clase Node. Puede hacerlo sin la declaración de reenvío, si no desea tener argumentos predeterminados (es decir, eliminar Node * r = NullNode).
Otra opción usaría algo de herencia: crear una clase padre (Nodo) con dos hijos (NullNode y FullNode). Entonces, el ejemplo de nodo anterior sería el código para FullNode y el NullNode en el código anterior sería del tipo NullNode heredado de Node. Odio resolver problemas simples apelando a la herencia.
Entonces, la pregunta es: ¿cómo aplicar patrones de objetos nulos a estructuras de datos recursivas (clases) con argumentos predeterminados (que son instancias de esa misma clase) en C ++?
Solución
Use extern
:
extern Node* NullNode;
...
Node* NullNode = new Node();
Mejor aún, conviértalo en un miembro estático:
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();
Dicho esto, tanto en el código existente como en las enmiendas anteriores, se pierde una instancia de Node
. Podría usar auto_ptr
, pero eso sería peligroso debido al orden incierto de destrucción de las variables globales y estáticas (un destructor de algunos global puede necesitar Node :: Null
, y puede o puede que ya no se haya ido para entonces).
Otros consejos
He implementado un árbol recursivo (para JSON, etc.) haciendo algo como esto. Básicamente, su clase base se convierte en " NULL " implementación, y su interfaz es la unión de todas las interfaces para el derivado. Luego tiene clases derivadas que implementan las piezas- '' DataNode '' implementa captadores y establecedores de datos, etc.
De esa manera, puede programar en la interfaz de la clase base y ahorrarse MUCHO dolor. Configura la implementación base para hacer toda la lógica repetitiva por usted, por ejemplo,
class Node {
public:
Node() {}
virtual ~Node() {}
virtual string OutputAsINI() const { return ""; }
};
class DataNode {
private:
string myName;
string myData;
public:
DataNode(const string& name, const string& val);
~DataNode() {}
string OutputAsINI() const { string out = myName + " = " + myData; return out; }
};
De esta manera no tengo que probar nada, simplemente llamo ciegamente " OutputAsINI ()
" ;. Una lógica similar para toda su interfaz hará que la mayoría de las pruebas nulas desaparezcan.
Invierte la jerarquía. Coloque el nodo nulo en la base:
class Node {
public:
Node() {}
virtual void visit() const {}
};
Luego, especialízate según sea necesario:
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();
Ejemplo de uso:
int main()
{
DataNode<char> a('A', new DataNode<char>('B'),
new DataNode<char>('C'));
a.visit();
return 0;
}
Salida:
$ ./node
B
A
C