Как получить/расширить рекурсивный класс

У меня есть рекурсивный класс, своего рода дерево, экземпляры которого являются переменными-членами. Например:

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

Может ли кто-нибудь предложить способ сделать это модульным способом без изменения реализации дерева. Можно изменить то, как он реализован в целом, если вам не нужно изменять его всякий раз, когда вы хотите расширить/получить от класса. Я вижу возможный способ сделать это, заставив класс шаблона T иметь методы для выполнения того, что я хочу, но это кажется уродливым. Должен быть лучший способ.

Я совершенно счастлив, если кто-то укажет, как я упустил из виду что-то очевидное. Это определенно похоже на то, что у меня есть.

Изменить: дело не в том, как реализовать функцию printInOrder(). Это был просто пример. Дело в том, как создать производный класс, чтобы дочерние элементы также были производным классом.


person cape1232    schedule 20.06.2011    source источник


Ответы (5)


Шаблон по типу узла.

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<T, NodeT> предлагает один и тот же интерфейс для всех NodeT, а DerivedTree<T> наследует от Tree<T, ...>.

Редактировать: Черт, потребовалось много усилий, чтобы предотвратить рекурсивное создание экземпляра Tree<T, NodeType>.

person Puppy    schedule 20.06.2011
comment
Да, именно этого понимания мне не хватало. Дочерние элементы также должны быть шаблонными, чтобы они также могли быть DerivedClass‹T›. Я выберу это как ответ после того, как попробую. - person cape1232; 21.06.2011

Сделайте printInOrder функцией virtual и сделайте ее доступной в Tree.

Это означает, что дочерние элементы дерева могут быть произвольными потомками Tree, и вызов printInOrder для них всегда будет вызывать переопределенную реализацию, если они ее предоставляют.

Основным недостатком является то, что все методы должны быть объявлены в Tree.

person Alexander Gessler    schedule 20.06.2011
comment
Беда в том, что пока leftChild и rightChild имеют тип Tree‹T›, они никогда не реализуют функцию printInOrder(). - person cape1232; 21.06.2011

Если вы хотите ненавязчивую печать, просто укажите для нее простую функцию шаблона:

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

В качестве альтернативы, если вы не хотите иметь его в качестве друга, предоставьте средства доступа для получения значения, а также левого и правого узлов дерева.

person reko_t    schedule 20.06.2011
comment
Друг может работать для этого конкретного экземпляра, но, как вы упомянули, мне нужно изменить Tree‹T›, чтобы объявить друга. Я хочу избежать изменения Tree. Конечно, я не против изменить то, как это реализовано в целом. Я не могу решить проблему без внесения каких-либо изменений, но я не хочу вносить изменения для каждого нового метода или другого расширения, которое мне понадобится позже. - person cape1232; 21.06.2011
comment
Вам бы не пришлось; вам нужно сделать его другом только один раз. Кроме того, если у вас есть функции доступа, такие как getValue(), getLeftChild() и т. д., он даже не должен быть другом, и в этом случае вы даже можете сделать эти функции виртуальными, и они будут работать правильно, если вы наследуете от дерева. - person reko_t; 21.06.2011

Это приемлемо для вас?

template <class T>void DerivedClass<T>::printInOrder()
{   
    ((DerivedClass<T>*)leftChild)->printInOrder();   
    std::cout << this->m_value << std::endl;   
    ((DerivedClass<T>*)rightChild)->printInOrder();
}
person Ajay    schedule 20.06.2011
comment
Это допустимо только в том случае, если leftChild и rightChild фактически были выделены как DerivedClass<T>. Если это не так, то притворяться, что они были с гипсом, незаконно. Это может функционировать просто отлично, но это определенно не является законным в соответствии со стандартом. - person Nicol Bolas; 21.06.2011
comment
Как работает мой код, leftChild и rightChild не будут выделены как DerivedClass‹T›, поэтому приведение не будет работать в любом случае. - person cape1232; 21.06.2011
comment
Я считаю, что не имеет значения, выделяются ли дочерние элементы как тип Tree или DerivedClass. Вы вызываете функцию, передаете ей действительное это значение и получаете доступ к m_value, который всегда будет доступен. Функция printfInOrder после приведения будет отображаться компилятором для ее вызова. Что, если printfIfOrder принимает указатель (то есть этот указатель) - место в памяти для printIfOrder не меняется. - person Ajay; 21.06.2011

Вы можете просто написать функцию-член, которая принимает экземпляр для печати:

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);
}
person Ferdinand Beyer    schedule 20.06.2011
comment
Я не думаю, что это работает. tree-›leftChild и tree-›rightChild по-прежнему не относятся к типу DerivedClass‹T›, поэтому у них нет члена printInOrder(). - person cape1232; 21.06.2011
comment
Эээ да, вы правы, спасибо. Я забыл адаптировать эти строки после копирования исходного кода. - person Ferdinand Beyer; 21.06.2011