What C ++ template to use for a library that allows you to extend its classes?

I am trying to break some code from my C ++ modeling software into a library, so it can be used more flexibly. The simulation is based on a Lattice , consisting of a number of Node , which contain lists of pointers to their Neighbor s. Although neighbors are also nodes, I would like to have a small wrapper class around the *Node pointer to implement additional logic / fields (e.g. bool is_neares_neighbor or so on.

The architecture of my class therefore looks something like this:

 class Lattice { private: vector<Node> _nodes; }; class Node { private: vector<Neighbor> _neighbors; }; class Neighbor { private: Node * _node; }; 

So far so good. Now I want my library to handle all the lattice-related logic, but nothing more. However, when using the library in some project, three classes ( Lattice , Node , Neighbor ) will carry more logic and fields. Thus, the user must inherit these classes and implement their custom materials, while the library still handles all the necessary lattice-related logic.

What is the recommended way to do this? Are the patterns consistent? In a template situation, my class hierarchy will look like this:

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

As you can see, both Node and Neighbor must know each other's type, which is a circular condition that I don’t know how to deal with. In addition, the entire library will have to live in the header files.

How are situations like this in the C ++ world in the most elegant way?

+5
source share
2 answers

I think you want the template type to be “some other type” that your lattice library does not know and does not care. So instead of the user fetching from Node, instead, you use template <class t_data> for all your classes.

You said to yourself: "Neighbor" is Node, so you can define everything in terms of Node <t_data>.

You should also consider how you are going to build the grill, and how you will return information about it. Here is an example when I assume that you create a grid by creating grid nodes, creating nodes, with the ability to connect them to existing nodes during creation.

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

I really don’t understand Neighbor, because either it is Node or it includes two nodes, so I will leave only one.

+1
source

I would recommend you use a combination of Composite / Visitor templates:

  • Composite helps you determine the hierarchy of the Lattice / Node / Neighbor / Custom_User_Node_Derived classes that can be processed evenly (regardless of content).
  • The visitor will help you bypass the hierarchy and encapsulate the logic for your Lattice / Node / Neighbor class, as well as allow the user to define new operations that you cannot yet predict.

This is a particularly recommended approach when you want to expand a library (or set of classes) on which you have no control. In my opinion, this is one of what you want your user to be able to.

0
source

Source: https://habr.com/ru/post/1243061/


All Articles