空对象模式、递归类和前向声明

发布于 2024-08-13 04:32:37 字数 563 浏览 7 评论 0原文

我有兴趣执行如下操作,以遵守 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++ 中将空对象模式应用于具有默认参数(它们是同一类的实例!)的递归数据结构(类)?

I'm interested in doing something like the following to adhere to a Null Object design pattern and to avoid prolific NULL tests:

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

Of course, as written, NullNode has different memory locations before and after the Node class declaration. You could do this without the forward declaration, if you didn't want to have default arguments (i.e., remove Node *r=NullNode).

Another option would use some inheritence: make a parent class (Node) with two children (NullNode and FullNode). Then the node example above would be the code for FullNode and the NullNode in the code above would be of type NullNode inheriting from Node. I hate solving simple problems by appeals to inheritence.

So, the question is: how do you apply Null Object patterns to recursive data structures (classes) with default arguments (which are instances of that same class!) in C++?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

伴我老 2024-08-20 04:32:37

使用 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,并且它可能或者到那时可能还没有消失)。

Use extern:

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

Better yet, make it a static member:

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

That said, in both existing code, and amendments above, you leak an instance of Node. You could use auto_ptr, but that would be dangerous because of uncertain order of destruction of globals and statics (a destructor of some global may need Node::Null, and it may or may not be already gone by then).

迟月 2024-08-20 04:32:37

我实际上已经实现了一个递归树(用于 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()”。整个界面的类似逻辑将使大多数空测试消失。

I've actually implemented a recursive tree (for JSON, etc.) doing something like this. Basically, your base class becomes the "NULL" implementation, and its interface is the union of all interfaces for the derived. You then have derived classes that implement the pieces- "DataNode" implements data getters and setters, etc.

That way, you can program to the base class interface and save yourself A LOT of pain. You set up the base implementation to do all the boilerplate logic for you, e.g.

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

This way I don't have to test anything- I just blindly call "OutputAsINI()". Similar logic for your whole interface will make most of the null tests go away.

萌辣 2024-08-20 04:32:37

颠倒层次结构。将空节点放在底部:

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

Invert the hierarchy. Put the null node at the base:

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

Then specialize as needed:

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

Sample usage:

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

  a.visit();

  return 0;
}

Output:

$ ./node 
B
A
C
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文