Шаблон нулевого объекта, Рекурсивный класс и прямые объявления
Вопрос
Я заинтересован в том, чтобы сделать что-то вроде следующего, чтобы придерживаться шаблона проектирования нулевого объекта и избегать плодовитых нулевых тестов:
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