【问题标题】:How to derive from/extend a recursive class如何派生/扩展递归类
【发布时间】:2011-06-20 20:09:51
【问题描述】:

我有一个递归类,一种树,它有自己的实例作为成员变量。例如:

template<class T>
class Tree {
public: 
  /* Constructors, etc. */
protected:
  T m_value;
  Tree<T> *leftChild;
  Tree<T> *rightChild;
};

如果我想添加一个使用中序遍历打印所有值的方法,我可以这样做:

template <class T>
void Tree<T>::printInOrder()
{
   leftChild->printInOrder();
   std::cout << m_value << std::endl;
   rightChild->printInOrder();
}

但是,如果由于各种原因,我不能或不想更改 Tree 的实现怎么办?如果该类不是递归的,即不包含自身的实例,我可以从 Tree 派生并在派生类中实现一个新方法。但这种方法不适用于 Tree。

template <class T>
class DerivedClass : public Tree<T> {
public:
  void printInOrder();
}

template <class T>
void DerivedClass<T>::
printInOrder()
{
   this->leftChild->printInOrder();
   std::cout << this->m_value << std::endl;
   this->rightChild->printInOrder();
}

leftChild 和 rightChild 是 Tree 的实例,因此没有 printInOrder() 方法。

谁能建议一种在不更改 Tree 实现的情况下以模块化方式执行此操作的方法。可以更改它的一般实现方式,只要您在想要扩展/派生类时不必更改它。通过使模板类 T 具有执行我想要的事情的方法,我可以看到一种可能的方法,但这看起来很难看。一定有更好的办法。

我很高兴有人指出我忽略了一些明显的事情。确实感觉像我一样。

编辑:重点不是如何实现 printInOrder()。那只是一个例子。关键是如何派生一个类,使子类也是派生类。

【问题讨论】:

    标签: c++ inheritance recursion derived-class


    【解决方案1】:

    节点类型上的模板。

    template<typename T, typename NodeType = void> class Tree {
        NodeType node;
        T m_data;
    };
    template<typename T> class Tree<void> {
        struct Node {
            Tree<T, void>* left;
            Tree<T, void>* right;
        };
        Node node;
        T m_data;
    };
    template<typename T> struct DerivedNode {
         DerivedTree<T>* left;
         DerivedTree<T>* right;
    };
    template<typename T> class DerivedTree : public Tree<T, DerivedNode<T>> {
         // now left and right are of type DerivedTree<T>*.
    };
    

    这基于两个不变量 - Tree&lt;T, NodeT&gt; 为所有 NodeT 提供相同的接口,并且 DerivedTree&lt;T&gt; 继承自 Tree&lt;T, ...&gt;

    编辑:该死,为了防止 Tree&lt;T, NodeType&gt; 的递归实例化需要付出很多努力。

    【讨论】:

    • 是的,这就是我所缺少的洞察力。孩子也需要模板化,以便它们也可以是 DerivedClass。试一试后我会选择这个作为答案。
    【解决方案2】:

    printInOrder 设为virtual 函数并使其在Tree 中可用。

    这意味着树孩子可以是Tree 的任意后代,并且在它们上调用printInOrder 将始终调用被覆盖的实现,如果它们提供的话。

    主要缺点是所有方法都需要在Tree 中声明。

    【讨论】:

    • 麻烦的是只要leftChild和rightChild是Tree类型,就永远不会实现printInOrder()。
    【解决方案3】:

    如果你想要非侵入式打印,只需为其指定一个普通的模板函数:

    template <typename T>
    void TreePrinter(const Tree<T>& tree)
    {
        TreePrinter(tree.leftChild);
        std::cout << tree.m_value << std::endl;
        TreePrinter(tree.rightChild);
    }
    

    你显然需要一个 TreePrinter 作为朋友:

    template<class T>
    class Tree {
    public: 
      /* Constructors, etc. */
    protected:
      T m_value;
      Tree<T> *leftChild;
      Tree<T> *rightChild;
    
      friend template <typename U>
      TreePrinter(const Tree<U>&);
    };
    

    或者,如果您不将其作为朋友,请提供访问器来获取值以及树的左右节点。

    【讨论】:

    • 朋友可能会为这个特定的实例工作,但正如你提到的,它要求我修改 Tree 来声明朋友。我想避免改变树。当然,我不介意改变它的整体实施方式。如果不进行某种更改,我无法解决问题,但我不想为以后需要的每个新方法或其他扩展进行更改。
    • 您不必这样做;你只需要让它成为朋友一次。此外,如果您有诸如getValue()getLeftChild() 等访问器函数,它甚至不必成为朋友,在这种情况下,您甚至可以使这些函数成为虚拟函数,并且在您继承的情况下它会正常工作树。
    【解决方案4】:

    你可以接受吗?

    template <class T>void DerivedClass<T>::printInOrder()
    {   
        ((DerivedClass<T>*)leftChild)->printInOrder();   
        std::cout << this->m_value << std::endl;   
        ((DerivedClass<T>*)rightChild)->printInOrder();
    }
    

    【讨论】:

    • 只有在 leftChildrightChild 实际分配为 DerivedClass&lt;T&gt;s 时才合法。如果他们不是,那么假装他们有演员表是不合法的。现在,它可能功能很好,但根据标准它肯定是不合法的。
    • 我的代码的工作方式,leftChild 和 rightChild 不会被分配为 DerivedClass,所以在任何情况下强制转换都不起作用。
    • 我相信孩子被分配为 Tree 或 DerivedClass 类型并不重要。您正在调用一个函数,将有效的 this 值传递给它并访问始终可用的 m_value。转换后的函数printfInOrder 将由编译器映射以调用它。如果printfIfOrder 使用指针(即this 指针)怎么办?printIfOrder 在内存中的位置不会改变。
    【解决方案5】:

    您可以简单地编写一个成员函数来打印实例:

    template <class T>
    void DerivedClass::printInOrderHelper(const Tree<T>& tree)
    {
        printInOrderHelper(tree->leftChild);
        std::cout << tree->m_value << std::endl;
        printInOrderHelper(tree->rightChild);
    }
    

    在零参数重载中使用它:

    template <class T>
    void DerivedClass::printInOrder()
    {
        printInOrderHelper(*this);
    }
    

    【讨论】:

    • 我认为这行不通。 tree->leftChild 和 tree->rightChild 仍然不是 DerivedClass 类型,因此它们没有 printInOrder() 成员。
    • 呃,是的,你是对的,谢谢。复制您的原始代码后,我忘记调整这些行了。
    猜你喜欢
    • 2012-07-28
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-25
    相关资源
    最近更新 更多