Шаблон нулевого объекта, Рекурсивный класс и прямые объявления

StackOverflow https://stackoverflow.com/questions/1822143

Вопрос

Я заинтересован в том, чтобы сделать что-то вроде следующего, чтобы придерживаться шаблона проектирования нулевого объекта и избегать плодовитых нулевых тестов:

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

Конечно, как написано, NullNode имеет разные ячейки памяти до и после объявления класса Node.Вы могли бы сделать это без прямого объявления, если бы не хотели иметь аргументы по умолчанию (т. Е. Удалить Node *r=NullNode).

Другим вариантом было бы использовать некоторое наследование:создайте родительский класс (Node) с двумя дочерними (NullNode и FullNode).Тогда приведенный выше пример узла был бы кодом для FullNode, а NullNode в приведенном выше коде имел бы тип NullNode, наследуемый от Node.Я ненавижу решать простые проблемы, апеллируя к наследству.

Итак, вопрос в том,:как вы применяете шаблоны нулевых объектов к рекурсивным структурам данных (классам) с аргументами по умолчанию (которые являются экземплярами того же класса!) в C ++?

Это было полезно?

Решение

Используйте extern :

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

Еще лучше, сделайте его статическим членом:

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

Тем не менее, как в существующем коде, так и в приведенных выше изменениях, вы пропускаете экземпляр Node . Вы можете использовать auto_ptr , но это было бы опасно из-за неопределенного порядка уничтожения глобальных и статических данных (для деструктора некоторых глобальных типов может потребоваться Node :: Null , и это может или, возможно, уже не ушел к тому времени).

Другие советы

Я фактически реализовал рекурсивное дерево (для JSON и т. д.), выполняя что-то вроде этого. По сути, ваш базовый класс становится "NULL". реализация, и его интерфейс представляет собой объединение всех интерфейсов для производного. Затем у вас есть производные классы, которые реализуют куски - " DataNode " реализует методы получения и установки данных и т. д.

Таким образом, вы можете запрограммировать интерфейс базового класса и сэкономить много боли. Вы настроили базовую реализацию, чтобы сделать всю шаблонную логику за вас, например.

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; }
};

Таким образом, мне не нужно ничего проверять - я просто слепо вызываю " OutputAsINI () " ;. Подобная логика для всего вашего интерфейса уберет большинство нулевых тестов.

Переверните иерархию.Поместите нулевой узел в основание:

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

Затем специализируйтесь по мере необходимости:

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

Пример использования:

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

  a.visit();

  return 0;
}

Выходной сигнал:

$ ./node 
B
A
C
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top