Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which C++ pattern to use for a library that allows extending its classes?

I am trying to split some code off my C++ simulation software into a library, so it can be used more flexibly. The simulation is based on a Lattice, consisting of a number of Nodes which hold lists of pointers to their Neighbors. Although neighbors are also nodes, I'd like to have a little wrapper class around the *Node pointer in order to implement additional logic/fields (e.g., bool is_neares_neighbor or so.

My class architecture looks therefore 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 would like my library to handle all the lattice-related logic, but nothing else. However, when using the library in some project, the three classes (Lattice, Node, Neighbor) will carry more logic and fields. The user should therefore be able to inherit from these classes and implement his/her custom stuff, while the library still handles all the necessary lattice-related logic.

What is the recommended way to do that? Are templates appropriate here? In a templated situation, my class hierarchy would 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 need to know the type of each other, which is a circular condition I have no idea how to deal with here. In addition, the whole library would have to live in header files.

How are situations like these dealt with in the C++ world in the most elegant way?

like image 728
janoliver Avatar asked Nov 09 '22 20:11

janoliver


1 Answers

I'm thinking you want the template type to be the "some other type" that your lattice library does not know or care about. So instead of having the user derive from Node, instead you use template<class t_data> for all your classes.

You said yourself, Neighbor is a Node, so you should be able to define everything in terms of Node<t_data>.

You should also consider how you are going to construct the lattice, and how you will return info about it. Here is an example where I assume you create the lattice by having the lattice class create nodes, with the option to connect them to existing nodes at the time of 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 don't really understand Neighbor, because either it is a Node, or it involves two nodes, so I'll leave that one alone.

like image 138
Kenny Ostrom Avatar answered Nov 14 '22 21:11

Kenny Ostrom