Circular double linked list with smart pointers in c++ - c++

Is it possible to create a circular doubly-linked list using smart pointers in C++
struct Node {
int val;
shared_ptr<Node> next;
weak_ptr prev;
};
shared_ptr<Node> head;
But this will have a circular reference of shared pointers and thus not deallocate correctly.

Make the circular linked list a class itself (with whatever operations you need to build it, like append). Have its destructor break the link by setting tail->next = nullptr. It should not matter which link you break, so if you're not using a head and tail, just set any one of them nullptr, and you're good.
In my testing, I made a circular linked list, and the nodes did not destruct. Then at the end, I added tail->next = nullptr before it exited, and all the destructors fired correctly.

My original posted answer was rather light on details. This one gives a proper explanation of how you can achieve a circular linked list without a memory leak and still adhere to the Rule of Zero. The answer is basically the same, using a sentinel, but the mechanism is a little more involved than I had originally let on.
The trick is to use a sentinel type that behaves just like a list node, but in fact does not really have a shared pointer to the head of the list. To achieve this, the node class should be separated into a behavior object and a state object.
class NodeState {
std::shared_ptr<Node> next_;
std::weak_ptr<Node> prev_;
int value_;
NodeState (int v) : value_(v) {}
NodeState (std::shared_ptr<Node> p) : next_(p), prev_(p) {}
//...
};
class Node {
virtual ~Node () = default;
virtual NodeState & state () = 0;
std::shared_ptr<Node> & next () { return state().next_; }
std::weak_ptr<Node> & prev () { return state().prev_; }
int & value () { return state().value_; }
void insert (const std::shared_ptr<Node> &p) {
//...
}
};
Now, you can define a node implementation and a sentinel implementation.
class NodeImplementation : public Node {
NodeState state_;
NodeState & state () { return state_; }
NodeImplementation (int v) : state_(v) {}
//...
};
class NodeSentinel : public Node {
List &list_;
NodeSentinel (List &l) : list_(l) {}
NodeState & state () { return list_.sentinel_state_; }
};
The list itself contains a NodeState used by the sentinel object. Upon initialization, the list creates a sentinel object and initializes its state.
class List {
//...
NodeState sentinel_state_;
std::shared_ptr<Node> head () { return sentinel_state_.next_; }
std::shared_ptr<Node> sentinel () {
return std::shared_ptr<Node>(head()->prev());
}
//...
public:
List () : sentinel_state_(std::make_shared<NodeSentinel>(*this)) {}
//...
void push_front (int value) {
head()->insert(std::make_shared<NodeImplementation>(value));
}
void push_back (int value) {
sentinel()->insert(std::make_shared<NodeImplementation>(value));
}
//...
};
So, what does this organization do? It avoids the issue of a circular reference by using a sentinel node to act as the break. While the tail of the list points to the sentinel object, the sentinel object itself does not point to anything. Instead, it uses the state of the list itself to determine its next and previous neighbors.
Thus, the circular shared pointers only persists as long as the list exists. Once the list is destroyed, Item A loses its reference, and via the domino effect, Sentinel itself will be destroyed.
A fundamental point is that the sentinel object itself must never be exposed to the user of the list interface directly. It should remain internal to the list object at all times. It essentially represents end() in an STL like container, and logically, it can never be removed from the list (until the list itself is destroyed). In practice, this means removal operations on the list need to exit early if the passed in iterator represents the sentinel.
Demo
Try It Online

It is also possible to define a member function next() which can select between a shared or weak pointer.
#include <iostream>
#include <memory>
using namespace std;
struct T {
int n_;
shared_ptr<T> next_;
weak_ptr<T> weaknext_;
T(shared_ptr<T> next, int n) : next_(next), n_(n) {};
auto next() {
if (next_ == nullptr)
return shared_ptr<T>(weaknext_);
return next_;
}
~T() { cout << n_ << "ok\n"; }
};
int main() {
auto p0 = make_shared<T>(nullptr, 1);
auto p1 = make_shared<T>(p0, 2);
auto p2 = make_shared<T>(p1, 3);
p0->weaknext_ = p2; //makes the list circular
auto p = p2;
for (int i = 0; i < 5; ++i) {
cout << p->n_ << "\n";
p = p->next();
}
}

Related

Implement iterator of a circular list

I'm trying to implement a class of Circular List with a nested class of iterator and I wrote like this:
template <class T>
class CircularList {
struct Item {
T data;
Item* next;
};
Item* head;
int size;
public:
CircularList() {
head = new Item();
head->next = head;
}
int sizeList() { return size; }
void push(T data) {
Item* i = new Item();
i->data = data;
i->next = head->next;
head->next = i;
size++;
}
class CircularListIterator {
Item* p;
CircularListIterator() {
p = head->next;
}
bool hasNext() {
if(p->next != head) {
return true;
}
return false;
}
T next() {
T data_temp = p->data;
p = p->next;
return data_temp;
}
};
CircularListIterator* iterator() {
return new CircularListIterator();
}
};
int main() {
CircularList<string>* letters = new CircularList<string>;
letters->push("d");
letters->push("c");
letters->push("b");
letters->push("a");
Iterator<string>* it= new Iterator<string>;
it = letters->iterator();
while (it->hasNext()) {
cout<< it->next() << "," << endl;
}
return 0;
}
But the Iterator is not working when I try to create an iterator in the main function, It said that it wasn't declared in the scope and has no member of it.
Assuming by "in the main class" you mean in the main function, the problem is quite straightforward: you're trying to construct a ::Iterator<string>, but there is no class in the global namespace (or anywhere else, in this code sample) called Iterator! You could try constructing a CircularList<string>::CircularListIterator - that's at least a class that exists - but it wouldn't work because the iterator needs to be associated with a CircularList object for it to be able to access member variables like head.
The correct thing to do here is to promote the iterator function - the one that returns a CircularListIterator* - out of the CircularListIterator class and into the CircularList class. Then, in your main function, you can call letters->iterator() and it'll return a CircularListIterator* for the letters object.
Now, CircularListIterator doesn't inherit from any other iterator classes - neither the (nonexistent-in-this-code Iterator you've typed it as, nor the C++ std::iterator or any of its variants) - so you can't assign it to it or probably even compile the code that references Iterator. To make CircularListIterator a subclass of std::iterator, you'll need to extend std::iterator<Category, T> with the appropriate category. See https://www.cplusplus.com/reference/iterator/iterator/ for more information on the std::iterator class template, including an example of implementing it.

C++ destructor being called unexpectedly doubly linked list

doubly_linked_list::~doubly_linked_list()
{
list_item* current = head;
while (current)
{
list_item* next = current->Get_Next();
delete current;
current = next;
}
}
I am writing a dynamic graph in C++ with the usage of a doubly linked list to save the Graph nodes, however my code keeps on failing as it calls the destructor more times than it should be calling it, for example, during my destructor for the graph
Dynamic_Graph::~Dynamic_Graph()
{
edges.~doubly_linked_list();
nodes.~doubly_linked_list();
}
the destructor for the doubly linked list is called more than 2 times, despite it being called explicitly twice.
then i have the function for insertion of an edge:
Graph_Edge* Dynamic_Graph::Insert_Edge(Graph_Node* from, Graph_Node* to)
{
Graph_Edge* new_edge = new Graph_Edge(from, to);
from->Get_out_Nodes().List_Insert_After(to, from->Get_out_Nodes().Get_Tail());
to->Get_in_Nodes().List_Insert_After(from, to->Get_in_Nodes().Get_Tail());
edges.List_Insert(new_edge);
return new_edge;
}
after adding the to node to the adjacency list of the from node the code unexplainably calls the destructor for a doubly linked list and deletes this insertion, however i am not sure why.
here are the headers for the graph and the linked list:
class doubly_linked_list
{
private:
list_item* head;
list_item* tail;
public:
doubly_linked_list() { this->head = NULL; this->tail = NULL; }
doubly_linked_list(list_item* head){ this->head = head; this->tail = NULL; }
~doubly_linked_list();
list_item* Get_Head() { return head; }
list_item* Get_Tail() { return tail; }
void Set_Head(list_item* h) { head = h; }
void List_Insert(list_item* x);
void List_Insert_After(list_item* x, list_item* y);
void List_Delete(list_item* x);
};
class Dynamic_Graph
{
protected:
doubly_linked_list edges;
doubly_linked_list nodes;
public:
Dynamic_Graph() { edges = doubly_linked_list(); nodes = doubly_linked_list(); }
~Dynamic_Graph();
Graph_Node* Insert_Node(unsigned node_key);
void Delete_Node(Graph_Node* node);
Graph_Edge* Insert_Edge(Graph_Node* from, Graph_Node* to);
void Delete_Edge(Graph_Edge* edge);
Rooted_Tree* SCC() const;
Rooted_Tree* BFS(Graph_Node* source) const;
};
any help would be welcome
EDIT:
i removed the calls to the destructors, however i still am getting a destructor call in the insert after function and i am not sure why.
EDIT 2:
adding more relevant lines of code:
Graph_Edge* Dynamic_Graph::Insert_Edge(Graph_Node* from, Graph_Node* to)
{
Graph_Edge* new_edge = new Graph_Edge(from, to);
from->Get_out_Nodes().List_Insert_After(to, from->Get_out_Nodes().Get_Tail());
to->Get_in_Nodes().List_Insert_After(from, to->Get_in_Nodes().Get_Tail());
edges.List_Insert(new_edge);
return new_edge;
}
this is the function that triggers the error where it access a deleted pointer despite it not supposed to be deleted, it triggers after finishing inserting the "to" node to the "from" node adjacency list.
void doubly_linked_list::List_Insert_After(list_item* x, list_item* y)
{
if (!y)
{
head = x;
tail = x;
return;
}
if (y == tail)
{
tail = x;
}
if (y->Get_Next())
{
y->Get_Next()->Set_Prev(x);
}
x->Set_Next(y->Get_Next());
x->Set_Prev(y);
y->Set_Next(x);
}
this function is the insert after function in the doubly linked list.
EDIT 3:
i tried to recrating the issue via the smallest amount of possible function, this is what i got:
#pragma once
#include < cstddef >
class List_Item
{
protected:
List_Item* next;
List_Item* prev;
public:
List_Item(): next(NULL), prev(NULL) {}
List_Item* Get_Next() { return next; }
List_Item* Get_Prev() { return prev; }
void Set_Next(List_Item* next) { this->next = next; }
void Set_Prev(List_Item* prev) { this->prev = prev; }
List_Item(const List_Item& item) : next(item.next), prev(item.prev){}
List_Item& operator=(const List_Item& item) { this->next = item.next; this->prev = item.prev; return *this; }
};
class Doubly_Linked_List
{
protected:
List_Item* head;
List_Item* tail;
public:
Doubly_Linked_List() : head(NULL), tail(NULL) {}
~Doubly_Linked_List();
List_Item* Get_Head() { return head; }
List_Item* Get_Tail() { return tail; }
void Set_Head(List_Item* h) { head = h; }
void Set_Tail(List_Item* t) { tail = t; }
void insert(List_Item* item);
void insert_after(List_Item* item, List_Item* after);
Doubly_Linked_List(const Doubly_Linked_List& list) : head(list.head), tail(list.tail) {}
Doubly_Linked_List& operator=(const Doubly_Linked_List& list) { this->head = list.head; this->tail = list.tail; return *this; }
};
Doubly_Linked_List::~Doubly_Linked_List()
{
List_Item* item = head;
while (item)
{
List_Item* next = item->Get_Next();
delete item;
item = next;
}
}
void Doubly_Linked_List::insert(List_Item* item)
{
item->Set_Next(head);
if (head)
head->Set_Prev(item);
if (!head)
tail = item;
head = item;
item->Set_Prev(NULL);
}
void Doubly_Linked_List::insert_after(List_Item* item, List_Item* after)
{
if (!after)
{
head = item;
tail = item;
return;
}
if (after->Get_Next())
{
after->Get_Next()->Set_Prev(item);
}
else
{
tail = item;
}
item->Set_Next(after);
item->Set_Prev(after);
after->Set_Next(item);
}
#pragma once
#include "List_Item.h"
#include"Doubly_Linked_List.h"
class Graph_Node :
public List_Item
{
protected:
const unsigned key;
Doubly_Linked_List in_nodes;
Doubly_Linked_List out_nodes;
public:
Graph_Node(unsigned key): key(key) {}
Doubly_Linked_List Get_In_Nodes() { return in_nodes; }
Doubly_Linked_List Get_Out_Nodes() { return out_nodes; }
};
class Graph_Edge :
public List_Item
{
protected:
Graph_Node* from;
Graph_Node* to;
public:
Graph_Edge(Graph_Node* f, Graph_Node* t): from(f), to(t) {}
Graph_Node* Get_From() { return from; }
Graph_Node* Get_To() { return to; }
};
int main()
{
unsigned node_key = 1;
Graph_Node* from = new Graph_Node(node_key++);
Graph_Node* to = new Graph_Node(node_key++);
from->Get_Out_Nodes().insert_after(to, from->Get_Out_Nodes().Get_Tail());
to->Get_In_Nodes().insert_after(from, to->Get_In_Nodes().Get_Tail());
}
You are not supposed to call destructors manually. They are called automatically for you.
You only need to use delete (which also calls the destructor) and only on objects that you have actually created with a call to new. Every other object will be destroyed automatically when the end of its scope is reached and its destructor will then be called automatically. This is also true for members of classes.
The only exception here is if you used a placement-new to create the object or if you intend to reuse its storage. But you probably don't intend to do something like this.
Remove the destructor of Dynamic_Graph completely. It is not needed.
Your class doubly_linked_list violates the rule of 0/3/5. The rule says that if you define a custom destructor, then you should also define a custom copy(/move) constructor and copy(/move) assignment operator.
If you violate this rule and you happen to make an implicit or explicit copy of an object of that class, then you are likely going to have undefined behavior because the destructors will be called twice and try to delete memory twice.
After question edit with a full example:
In the code you are showing now the destructor for Doubly_Linked_List is called four times (see https://godbolt.org/z/KmStwy). These calls are because Get_In_Nodes and Get_Out_Nodes return Doubly_Linked_List lists by-value as copies of the class members of Graph_Node. Since you call these functions four times in main, there are four Doubly_Linked_List temporary objects that need to be destructed (which the compiler does automatically).
You did not properly implement Doubly_Linked_List(const Doubly_Linked_List& list) (and the copy assignment operator) to actually copy the whole list, so your program still has undefined behavior. You need to implement the copy operations in such a way that the whole list is being deep copied, otherwise the destructor call of both the originial and the copy will delete the same nodes.
Alternatively you can define the copy operations as deleted:
Doubly_Linked_List(const Doubly_Linked_List& list) : head(list.head), tail(list.tail) = delete
Doubly_Linked_List& operator=(const Doubly_Linked_List& list) = delete;
in which case the compiler will not allow copying and give a compile-time error if you try to copy. But your current implementation is just as broken as it was before. You basically just copied what the implicit copy operations do. Read the link about the rule of 0/3/5 again and carefully since it is extremely important to avoid undefined behavior.
The fact that the destructor is called multiple times is not by itself a problem. That is normal if copies are involved. The problem is only that your class copy operations are broken. If you don't want to create copies in the Get_In_Nodes and Get_Out_Nodes though, then return by-reference instead.
In general you should not use new/delete in the way that you are doing in modern C++. Instead you can use std::make_unique and std::unique_ptr for owning pointers and if you do so, then none of your shown classes will need custom destructors (at least not custom destructors that have effective behavior different from the implicit one) and you can always use the rule of 0, meaning that you don't need to implement any of the copy operations either.
And if this is anything but a learning exercise, you shouldn't write your own list in the first place. std::list works fine and is almost surely superior to what you are coming up with.

Returning shared pointer reference breaks outside of class methods

I have a tree in C++ and a method in the Tree that returns a shared_ptr reference to a new leaf whenever that leaf is added to the tree. When I use this inside other methods of the Tree class it works fine, but when I try to call it from main, it crashes, despite the code seemingly doing the same thing.
Here's the Node and Tree classes:
#include <iostream>
#include <vector>
#include <memory>
class Node
{
public:
int value;
std::vector<std::shared_ptr<Node> > children;
Node(int value): value{value} {}
};
class Tree
{
public:
std::shared_ptr<Node> root;
Tree(): root {nullptr} {}
std::shared_ptr<Node> CreateLeaf(int value)
{
return std::make_shared<Node>(value);
}
std::shared_ptr<Node>& AddLeaf(int value, std::shared_ptr<Node>& ptr)
{
if(ptr == nullptr)
{
ptr = std::move(CreateLeaf(value));
return ptr;
}
else
{
std::shared_ptr<Node> newLeaf = CreateLeaf(value);
ptr->children.push_back(std::move(newLeaf));
return ptr->children.back();
}
}
void otherMethod()
{
AddLeaf(1, root);
std::shared_ptr<Node>& temporary = AddLeaf(2, root);
std::cout << "temporary->value: " << temporary->value << std::endl;
}
};
If the main function is:
int main()
{
Tree t;
t.otherMethod();
}
Then the program runs correctly.
However, if the main function is:
int main()
{
Tree t;
t.AddLeaf(1, t.root);
std::shared_ptr<Node>& b = t.AddLeaf(2, t.root);
std::cout << "b->value = " << b->value << std::endl;
}
the program crashes, despite it doing pretty much the same thing. It seems like AddLeaf is just storing the nullptr in b, despite it being a reference to a persistent object, t.root->children[0]. Why is it doing that?
Having references to elements in a vector, or any self-resizing container, is dangerous. I've dealt with this when I created my first game in C++.
Essentially what happens is:
Your vector resizes when adding a new shared_ptr, which may cause an operation to allocate more memory. During that procedure the currently existing vector is destructed and allocated on possibly a different location in memory.Meaning that all currently existing pointers or references to elements in the vector become invalidated and may crash your program at some point.
Passing out references to shared_ptr's in a vector basically promotes undefined behavior. A smarter move would be to just return a new shared_ptr and have the reference counter just increment.That way, when the vector resizes, no reference in your program gets invalidated.
Removing the reference should help:
std::shared_ptr<Node> AddLeaf(int value, std::shared_ptr<Node>& ptr)
{
if(ptr == nullptr)
{
ptr = std::move(CreateLeaf(value));
return ptr;
}
else
{
std::shared_ptr<Node> newLeaf = CreateLeaf(value);
ptr->children.push_back(std::move(newLeaf));
return ptr->children.back();
}
}
Anyway, as I see you're creating a tree, perhaps you would like to take a look at code I've written in an (abandoned) project: https://github.com/wvanbreukelen/LexMe/blob/feature-tree-node-vector-specialization/LexMe/TreeNode.hIt's a quite complete implementation for a generic tree with friendliness towards move semantics and iterators.

Breaking cyclic references with std::weak_ptr and alias constructor: sound or problematic?

I have not yet found the following way of breaking cyclic references explained on any major C++ forum/blog, like on GotW, so I wanted to ask whether the technique is known, and what are its pro and cons?
class Node : public std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getParent() {
return parent.lock();
}
// the getter functions ensure that "parent" always stays alive!
std::shared_ptr<Node> getLeft() {
return std::shared_ptr<Node>(shared_from_this(), left.get());
}
std::shared_ptr<Node> getRight() {
return std::shared_ptr<Node>(shared_from_this(), right.get());
}
// add children.. never let them out except by the getter functions!
public:
std::shared_ptr<Node> getOrCreateLeft() {
if(auto p = getLeft())
return p;
left = std::make_shared<Node>();
left->parent = shared_from_this();
return getLeft();
}
std::shared_ptr<Node> getOrCreateRight() {
if(auto p = getRight())
return p;
right = std::make_shared<Node>();
right->parent = shared_from_this();
return getRight();
}
private:
std::weak_ptr<Node> parent;
std::shared_ptr<Node> left;
std::shared_ptr<Node> right;
};
From the outside, the user of Node will not notice the trick with using the aliasing constructor in getLeft and getRight, but still the user can be sure that getParent always returns a non-empty shared pointer, because all pointers returned by p->get{Left,Right} keep the object *p alive for the lifetime of the returned child pointer.
Am I overlooking something here, or is this an obvious way to break cyclic references that has been documented already?
int main() {
auto n = std::make_shared<Node>();
auto c = n->getOrCreateLeft();
// c->getParent will always return non-null even if n is reset()!
}
The shared_ptr<Node> returned by your getParent owns the parent, not the parent's parent.
Thus, calling getParent again on that shared_ptr can return an empty (and null) shared_ptr. For example:
int main() {
auto gp = std::make_shared<Node>();
auto p = gp->getOrCreateLeft();
auto c = p->getOrCreateLeft();
gp.reset();
p.reset(); // grandparent is dead at this point
assert(c->getParent());
assert(!c->getParent()->getParent());
}
(The inherited shared_from_this also passes out shared_ptrs that owns the node rather than its parent, but I suppose you can make it harder to mess up by a private using declaration and ban it by contract.)

Copy constructor and dynamic allocation

I would like to ask you how to write a copy constructor (and operator = ) for the following classes.
Class Node stores coordinates x,y of each node and pointer to another node.
class Node
{
private:
double x, y;
Node *n;
public:
Node (double xx, double yy, Node *nn) : x(xx), y(yy), n(nn) {}
void setNode (Node *nn) : n(nn) {}
...
};
Class NodesList (inherited from std:: vector) stores all dynamically allocated Nodes
class NodesList : public std::vector<Node *>
{}
The main program:
int main()
{
Node *n1 = new Node(5,10,NULL);
Node *n2 = new Node(10,10,NULL);
Node *n3 = new Node(20,10,NULL);
n1->setNode(n2);
n2->setNode(n3);
n3->setNode(n2);
NodesList nl1;
nl1.push_back(n1);
nl1.push_back(n2);
nl1.push_back(n3);
//Copy contructor is used, how to write
NodesList nl2(nl1);
//OPerator = is used, how to write?
NodesList nl3 = nl1;
}
I do not want to create a shallow copy of each node but a deep copy of each node. Could I ask you for a sample code with copy constructor?
Each node can be pointed more than once. Let us have such situation, when 3 nodes n[1], n[2], n[3] are stored in the NodesList nl1:
n[1] points to n[2]
n[2] points to n[3]
n[3] points to n[2]
A] Our copy constructor process the node n[1]. It creates a new object n[1]_new represented by the copy of the old object n[1]_old. The node n[2] pointed from n[1]_old still does not exist, so n[2]_new must be also created... The pointer from n1_new to n2_new is set.
B] Then second point n[2] is processed. It can not be created twice, n[2]_new was created in A]. But pointed node n[3] does not exist, so the new object n[3]_new as a copy of an old object n[3]_old is created. The pointer from n2_new to n3_new is set.
C] Node n[3]_new has already been created and n[2]_new. The pointer from n3_new to n2_new is set and no other object will be created...
So the copy constructor should check whether the object has been created in the past or has not...
Some reference counting could be helpful...
There is my solution of the problem. A new data member n_ref storing a new verion of the node n was added:
class Node
{
private:
double x, y;
Node *n, *n_ref;
public:
Node (double xx, double yy, Node *nn) : x(xx), y(yy), n(nn) {n_ref = NULL;}
Node * getNode() {return n;}
Node * getRefNode () {return n_ref;}
void setNode (Node *nn) {this->n = nn;}
void setRefNode (Node *nn) {this->n_ref = nn;}
The copy constructor creates a shallow copy of the node:
Node (const Node *node)
{
x = node->x;
y = node->y;
n = node->n;
n_ref = node->n_ref;
}
The copy constructor for NodesList
NodesList::NodesList(const NodesList& source)
{
const_iterator e = source.end();
for (const_iterator i = source.begin(); i != e; ++i) {
//Node* n = new Node(**i);
//Node n still has not been added to the list
if ((*i)->getRefNode() == NULL)
{
//Create node
Node *node = new Node(*i);
//Add node to the list
push_back(node);
//Set this note as processed
(*i)->setRefNode(node);
//Pointed node still has not been added to the list
if ((*i)->getNode()->getRefNode() == NULL)
{
//Create new pointed node
Node *node_pointed = new Node ((*i)->getNode());
//Add node to the list
push_back(node_pointed);
//Set pointer to n
node->setNode(node_pointed);
//Set node as processed
((*i)->getNode())->setRefNode(node_pointed);
}
//Pointed node has already been added to the list
else
{
//Set pointer to node n
node->setNode((*i)->getRefNode());
}
}
//Node n has already been added to the list
else
{
//Get node
Node * node = (*i)->getRefNode();
//Pointed node still has not been added
if ((*i)->getNode()->getRefNode() == NULL)
{
//Create new node
Node *node_pointed = new Node ((*i)->getNode());
//Add node to the list
push_back(node_pointed);
//Set pointer to n
node->setNode(node_pointed);
//Set node as processed
((*i)->getNode())->setRefNode(node_pointed);
}
//Pointed node has already been added to the list
else
{
//Set pointer to n
node->setNode((*i)->getNode()->getRefNode());
}
}
}
}
Perform a shallow copy in NodeList::NodeList(const NodeList&) and you don't have to worry about cycles breaking the copy operation. Disclaimer: the following is untested, incomplete and may have bugs.
class NodeList {
private:
typedef std::vector<Node*> Delegate;
Delegate nodes;
public:
NodeList(int capacity=16) : nodes() { nodes.reserve(capacity); }
NodeList(const NodeList& from);
virtual ~NodeList();
NodeList& operator=(const NodeList& from);
/* delegated stuff */
typedef Delegate::size_type size_type;
typedef Delegate::reference reference;
typedef Delegate::const_reference const_reference;
typedef Delegate::iterator iterator;
typedef Delegate::const_iterator const_iterator;
size_type size() const { return nodes.size(); }
iterator begin() { return nodes.begin(); }
const_iterator begin() const { return nodes.begin(); }
iterator end() { return nodes.end(); }
const_iterator end() const { return nodes.end(); }
// ...
};
NodeList::NodeList(const NodeList& from)
: nodes(from.size()), flags(NodeList::owner)
{
std::map<Node*, Node*> replacement;
Delegate::const_iterator pfrom;
Delegate::iterator pto;
// shallow copy nodes
for (pfrom=from.begin(), pto=nodes.begin();
pfrom != from.end();
++pfrom, ++pto)
{
replacement[*pfrom] = *pto = new Node(**pfrom);
}
// then fix nodes' nodes
for (pto = nodes.begin(); pto != nodes.end(); ++pto) {
(*pto)->setNode(replacement[(*pto)->getNode()]);
}
}
NodeList::operator=(const NodeList&) can use the copy-swap idiom, the same as Tronic's Node::operator=(const Node&).
This design has a potential memory leak in that a copied NodeList is (initally) the only place that references its nodes. If a temporary NodeList goes out of scope, a poor implementation will leak the Nodes the list contained.
One solution is to proclaim that NodeLists own Nodes. As long as you don't add a Node to more than one NodeList (via NodeList::push_back, NodeList::operator[] &c), NodeList's methods can delete nodes when necessary (e.g. in NodeList::~NodeList, NodeList::pop_back).
NodeList::~NodeList() {
Delegate::iterator pnode;
for (pnode = nodes.begin(); pnode != nodes.end(); ++pnode) {
delete *pnode;
}
}
void NodeList::pop_back() {
delete nodes.back();
nodes.pop_back();
}
Another solution is to use smart pointers, rather than Node*. NodeList should store shared pointers. Node::n should be a weak pointer to prevent ownership cycles.
I would just use std::list<Node> instead of NodesList. Well, let's code...
NodesList::NodesList(const NodesList& source)
{
const_iterator e = source.end();
for (const_iterator i = source.begin(); i != e; ++i) {
Node* n = new Node(**i);
push_back(n);
}
}
Apparently each Node is only allowed to point to another Node in the same list? Otherwise the "deep copy" of a list needs more definition. Should it not be connected to the original NodeList? Should it not be connected to any original Node? Are copies of Nodes not in the list being copied added to some other list or free-floating?
If all the Node-to-Node pointers are constrained within the NodeList, then perhaps you should store indexes instead of pointers, then no special handling is required.
You should not inherit from standard library containers (because they lack virtual destructors). Instead, include them as member variables in your classes.
Since you want a deep copy, you need these: (rule of three)
Node(Node const& orig): x(orig.x), y(orig.y), n() {
if (orig.n) n = new Node(*orig.n);
}
Node& operator=(Node const& orig) {
// The copy-swap idiom
Node tmp = orig;
swap(tmp); // Implementing this member function left as an exercise
return *this;
}
~Node() { delete n; }
A better idea might be to avoid using pointers entirely and just put your nodes in a suitable container.