Search code examples
c++11shared-ptr

Is modelling a Network in C++ with shared pointers a sane design?


I am currently in the process of expanding my knowledge in C++. For this, I am coding a template Network. (The actual exercise is Project Euler's problem 107, if someone is interested.)

Now reading up about pointers in C++11, the following design for my class looks plausible:

template< typename T, size_t D >
class Network<T,D>
{
  public:
    Network();
    ~Network();
    bool add_edge( size_t, size_t, T );
    bool remove_edge( size_t, size_t );
    struct Node;
    struct Edge;
  private:
    vector<Node> network;
};

with the following preliminary implementation

template< typename T, size_t D >
struct Network<T,D>::Edge
{
  pair<shared_ptr<Node>,shared_ptr<Node>> vertices;
  T weight;
}

template< typename T, size_t D >
struct Network<T,D>::Node
{
  Node( size_t idx )
    index = idx;

  size_t index;
  vector<shared_ptr<Edge>> connections;
};

template< typename T, size_t D >
Network<T,D>::Network( )
{
  network.reserve( D );
  for( size_t s = 0; s <= D; s++ )
    network.push_back( Node(s) );
}

template< typename T, size_t D >
Network<T,D>::~Network() {}

Please be aware, this is just preliminary code; i haven't compiled anything yet.

However, the following questions arise:

  1. Is this a legitimate use of std::shared_ptr? Using it to count references from Edges to Nodes would make it easy to determine whether a node is isolated.
  2. In my model, might it be possible to exchange std::shared_ptr with std::weak_ptr? As far as I understand, no. In Edge, the shared_ptr<Node> is necessary to actually be able to count references, in my Nodes the shared_ptr<Edge> is necessary to actually keep referring to an Edge and not lose it.

  3. To a lesser extent, does using size_t D make sense for my template? (Apart from the small likelihood of using this model with multi-terabyte computers for addressing huge networks...)

  4. Even if this might attract opinionated answers, I'd be open to design alternatives.


Solution

  • I would say shared_ptr or any other smart pointer is a wrong tool here. Because:

    1. There is one clear owner of nodes and edges: the adjacency matrix. Store nodes in an array (like you do now) and edges in the matrix, both by value. Shared pointers are useful when establishing ownership is hard (and is symptomatic of a design that was not thought through).
    2. It is concise and more efficient to store the count of incoming/outgoing edges in the node itself.
    3. Constructing shared_ptr involves allocating the control block (where the reference counter and the deleter are stored) on the heap. shared_ptr size is that of two pointers. Reference counter maintenance involves atomic instructions, which introduce unnecessary latency in single-threaded use-cases. In other words, shared_ptr is one of the most expensive pointers available.