Какой шаблон C++ использовать для библиотеки, которая позволяет расширять ее классы?

Я пытаюсь разделить некоторый код из моего программного обеспечения для моделирования C++ в библиотеку, чтобы его можно было использовать более гибко. Моделирование основано на Lattice, состоящий из ряда Nodeы, которые содержат списки указателей на их Neighbors. Хотя соседи также являются узлами, я хотел бы иметь небольшой класс-обертку вокруг *Node указатель для реализации дополнительной логики / полей (например, bool is_neares_neighbor или так.

Моя классовая архитектура выглядит примерно так:

class Lattice {
private:
    vector<Node> _nodes;
};

class Node {
private:
    vector<Neighbor> _neighbors;
};

class Neighbor {
private:
    Node * _node;
};

Все идет нормально. Теперь я хотел бы, чтобы моя библиотека обрабатывала всю логику, связанную с решеткой, но не более того. Тем не менее, при использовании библиотеки в каком-либо проекте, три класса (Lattice, Node, Neighbor) будет нести больше логики и полей. Следовательно, пользователь должен иметь возможность наследовать от этих классов и реализовывать свои пользовательские вещи, в то время как библиотека все еще обрабатывает всю необходимую логику, связанную с решеткой.

Каков рекомендуемый способ сделать это? Подходят ли здесь шаблоны? В шаблонной ситуации моя иерархия классов будет выглядеть так:

template<class N>
class Lattice {
private:
    vector<N> _nodes;
};

template<class NN>
class Node {
private:
    vector<NN> _neighbors;
};

template<class N>
class Neighbor {
private:
    N * _node;
};

Как видите, оба Node а также Neighbor Мне нужно знать тип друг друга, что является круговым условием. Я понятия не имею, как здесь иметь дело. Кроме того, вся библиотека должна была бы жить в заголовочных файлах.

Как такие ситуации решаются в мире C++ наиболее элегантным образом?

2 ответа

Решение

Я думаю, вы хотите, чтобы тип шаблона был "каким-то другим типом", который ваша библиотека решетки не знает или не заботится. Поэтому вместо того, чтобы пользователь наследовал от Node, вместо этого вы используете шаблон для всех ваших классов.

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

Вы также должны подумать, как вы собираетесь построить решетку, и как вы будете возвращать информацию о ней. Вот пример, где я предполагаю, что вы создаете решетку с помощью класса решетки, создающего узлы, с возможностью подключения их к существующим узлам во время создания.

#include <vector>
#include <memory>

template<class t_data>
class SetOfNodes;

template<class t_data>
class Node {
public:
    Node(t_data value, SetOfNodes neighbors) : _data(value), _neighbors(neighbors) {}
    is_nearest_neighbor(const Node &other){
        // is other in _neighbors?
        // is other the closest neighbor?
        return false;
    }
private:
    t_data _data;
    SetOfNodes _neighbors;
};

template<class t_data>
class SetOfNodes {
public:
    std::vector<std::shared_ptr<Node<t_data>>> _nodes;
};

template<class t_data>
class Lattice {
public:
    SetOfNodes get_all_nodes();
    void create_new_node(SetOfNodes neighbors);
private:
    SetOfNodes _nodes;
};

Я не совсем понимаю Neighbor, потому что это либо узел, либо он включает в себя два узла, поэтому я оставлю его в покое.

Я бы порекомендовал вам использовать комбинацию Composite / Visitor pattern:

  • Composite поможет вам определить иерархию классов Lattice/Node/Neighbor/Custom_User_Node_Derived, которые можно обрабатывать единообразно (независимо от содержимого)
  • Посетитель поможет вам просканировать иерархию и инкапсулировать логику для вашего класса Lattice / Node / Neighbor, а также позволит пользователю определить новые операции, которые вы пока не можете предсказать.

Это особенно рекомендуемый подход, когда вы хотите расширить библиотеку (или набор классов), над которыми у вас нет контроля. Насколько я понимаю, это то, что вы хотите, чтобы ваш пользователь мог.

Другие вопросы по тегам