我有兴趣做类似以下的事情来遵守Null对象设计模式并避免多产的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();
当然,正如所写,NullNode在Node类声明之前和之后具有不同的内存位置。如果您不想使用默认参数(例如,删除Node * r = NullNode),则可以在没有前向声明的情况下执行此操作。
另一种选择是使用一些继承:使用两个子节点(NullNode和FullNode)创建父类(Node)。然后上面的节点示例将是FullNode的代码,上面代码中的NullNode将是继承自Node的NullNode类型。我讨厌通过诉诸继承来解决简单的问题。
所以,问题是:如何在C ++中使用默认参数(同一类的实例!)将Null对象模式应用于递归数据结构(类)?
答案 0 :(得分:3)
使用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
,它可能已经或者可能已经消失了) 。
答案 1 :(得分:3)
我实际上已经实现了一个递归树(对于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()
”。整个界面的类似逻辑将使大多数空测试消失。
答案 2 :(得分:0)
反转层次结构。将空节点放在基数:
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