Question

Je souhaiterais utiliser l'une des méthodes suivantes pour adhérer à un modèle de conception Null Object et éviter les tests NULL prolifiques:

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();

Bien sûr, comme écrit, NullNode a différents emplacements de mémoire avant et après la déclaration de la classe Node. Vous pouvez le faire sans la déclaration forward si vous ne voulez pas avoir d'argument par défaut (c'est-à-dire, supprimez Node * r = NullNode).

Une autre option utiliserait une certaine héritage: créer une classe parent (Node) avec deux enfants (NullNode et FullNode). Ensuite, l'exemple de noeud ci-dessus serait le code pour FullNode et le NullNode dans le code ci-dessus serait du type NullNode héritant de Node. Je déteste résoudre des problèmes simples en faisant appel à l'héritage.

La question est donc de savoir comment appliquer les modèles d'objet Null aux structures de données (classes) récursives avec des arguments par défaut (qui sont des instances de la même classe!) en C ++.

Était-ce utile?

La solution

Utiliser extern :

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

Mieux encore, faites-en un membre statique:

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();

Cela dit, dans le code existant et dans les modifications ci-dessus, vous perdez une instance de Node . Vous pouvez utiliser auto_ptr , mais ce serait dangereux en raison de l'ordre de destruction incertain des globaux et de la statique (un destructeur de certains globaux peut avoir besoin de Node: Null et ou peut-être pas déjà parti à ce moment-là).

Autres conseils

J'ai en fait implémenté un arbre récursif (pour JSON, etc.) faisant quelque chose comme ça. En gros, votre classe de base devient la " NULL " mise en œuvre, et son interface est la réunion de toutes les interfaces pour le dérivé. Vous avez ensuite des classes dérivées qui implémentent les éléments " DataNode " implémente des getters et des setters de données, etc.

De cette façon, vous pouvez programmer sur l'interface de classe de base et vous épargner BEAUCOUP de douleur. Vous configurez l'implémentation de base pour qu'elle exécute toute la logique standard, par exemple

.
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 cette façon, je n'ai rien à tester, j'appelle aveuglément " OutputAsINI () ". Une logique similaire pour toute votre interface fera disparaître la plupart des tests nuls.

Inverser la hiérarchie. Placez le noeud nul à la base:

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

Puis spécialisez-vous au besoin:

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();

Exemple d'utilisation:

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

  a.visit();

  return 0;
}

Sortie:

$ ./node 
B
A
C
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top