I'm trying to correctly implement a simple linked list respecting the rule of 5. I get to about 3, although I already have my doubts here, but from there on, I'm on thin ice. As it seems like a fairly common topic, I was surprised I couldn't find a complete example. I've found bits and pieces, but no complete set. So if I get this sorted, it could serve as a future reference as well.
I've added an example class Data for some real life "complexity", because most examples just have a node with a single int and a pointer to the next item.
EDIT: I've completed the class with the code as shown below by PaulMcKenzie and it compiles ok in VS2019, but gives warning on the move constructor and assignment operator: C26439: This kind of function may not throw. Declare it 'noexcept' (f.6).
class Data
{
public:
int id;
string name;
float[5] datapoints;
};
class Node
{
public:
Node(Data d = { 0 }, Node* n = nullptr) : data(d), next(n) {};
Data& GetData() { return data; }
Node*& GetNext() { return next; }
private:
Data data;
Node* next;
};
class NodeList
{
public:
NodeList() :head(nullptr) {} // constructor
~NodeList(); // 1. destructor
NodeList(const NodeList& src); // 2. copy constructor
NodeList& operator=(const NodeList& src); // 3. copy assignment operator
NodeList(NodeList&& src); // 4. move constructor
NodeList& operator=(NodeList&& src); // 5. move assignment operator
void AddToNodeList(Data data); // add node
private:
Node* head;
};
void NodeList::AddToNodeList(Data data)
{
head = new Node(data, head);
}
NodeList::~NodeList()
{
Node* n = head, * np;
while (n != nullptr)
{
np = n->GetNext();
delete n;
n = np;
}
}
NodeList::NodeList(const NodeList & src) : head(nullptr)
{
Node* n = src.head;
while (n != nullptr)
{
AddToNodeList(n->GetData());
n = n->GetNext();
}
}
NodeList& NodeList::operator= (const NodeList& src)
{
if (&src != this)
{
NodeList temp(src);
std::swap(head, temp.head);
}
return *this;
}
NodeList::NodeList(NodeList&& src) : head{src.head}
{
src.head = nullptr;
}
NodeList& NodeList::operator=(NodeList&& src)
{
if (this != &src)
std::swap(src.head, head);
return *this;
}
The first thing to address is that your assignment operator is not correct. You're using the copy / swap idiom, but you forgot to do the copy.
NodeList& NodeList::operator=(NodeList src)
{
std::swap(head, src.head);
return *this;
}
Note the change from a const NodeList& to NodeList src as the argument. This will make the compiler automatically do the copy for us, since the parameter is passed by value.
If you still want to pass by const reference, the following change would need to be made:
NodeList& NodeList::operator=(const NodeList& src)
{
if ( &src != this )
{
NodeList temp(src); // copy
std::swap(head, temp.head);
}
return *this;
}
Note the additional test for self-assignment. It really isn't necessary, but may speed up the code (but again, no guarantee).
As to whether this is the most efficient way to do this, that is up for debate -- it all depends on the object. But one thing is for sure -- there will be no bugs, dangling pointers, or memory leaks if you use the copy/swap idiom (correctly).
Now onto the move functions:
To implement the missing functions, you should basically remove the contents from the existing object, and steal the contents from the passed-in object:
First, the move contructor:
NodeList::NodeList(Nodelist&& src) : head{src.head}
{
src.head = nullptr;
}
All we really want to do is steal the pointer from src, and then set the src.head to nullptr. Note that this will make src destructible, since src.head will be nullptr (and your destructor for NodeList handles the nullptr correctly).
Now for the move-assignment:
Nodelist& operator=(NodeList&& src)
{
if ( this != &src )
std::swap(src.head, head);
return *this;
}
We check for self assignment, since we don't want to steal from ourselves. In reality, we really didn't steal anything, only swapped things out. However unlike the assignment operator, no copy is done -- just a swap of the internals (this is basically what your incorrect assignment operator that was fixed earlier was doing). This allows the src to destroy the old contents when it's time for the src destructor to be invoked.
Note that after the move (either construction or assignment), the passed-in object is basically in a state that may or may not make the object usable or if not usable, stable (because potentially, the internals of the passed-in object have been changed).
The caller can still use such an object, but with all the risks of using an object that may or may not be in a stable state. Thus the safest thing for the caller is to let the object die off (which is why in the move constructor, we set the pointer to nullptr).
Related
I have the following List class that's supposed to hold a linked list. I'm experimenting and trying to learn more about move semantics.
I was wondering if I have implemented the move constructor and assignment operator properly.
What I want to achieve at the end, is have all contents emptied from an object to another.
template<typename T>
class List
{
public:
class Node {
public:
Node(T value, Node* prev, Node* next) : value_(value), prev_(prev), next_(next) {}
T value_;
Node* next_;
Node* prev_;
};
Node* head_;
Node* tail_;
//! Move constructor
List(List&& list) {
std::swap(head_, list.head_);
std::swap(tail_, list.tail_);
}
//! Move assignment operator
List& operator= (List&& list) {
std::swap(head_, list.head_);
std::swap(tail_, list.tail_);
return *this;
}
};
main.cpp
#include <iostream>
#include "List.h"
#include <string>
int main(){
List<int> l1;
List<int> l2;
//or calling the constructor: List<int> l2(std::move(l1));
l1.push_front(4);
l1.push_front(3);
l2 = std::move(l1); //all contents of l1 object are dumped into l2
}
For the class shown here, there are no members except tail and head, hence I'd say
You must initialize your tail and head before making the moving in the move ctor
List(List&& list) noexcept : head{ nullptr }, tail{ nullptr } {
std::swap(head_, list.head_);
std::swap(tail_, list.tail_);
}
The constructor and the assignment operator should be noexcept because the compiler may ignore them.
You may check for self-assignment in the beginning of the assignment operator, as follows
if (this == &rhs) {
return *this;
}
During a move operation, the object/variable being moved from must be left in a "valid but indeterminate state". However, in your move constructor, head_ and tail_ have not been initialized before swapping them into the source list that is being moved, thus leaving the source list in an invalid state after the move.
You need to change these declarations:
Node* head_;
Node* tail_;
To this:
Node* head_ = nullptr;
Node* tail_ = nullptr;
Also, you need to add a copy constructor, too:
//! Copy constructor
List(const List& list) {
Node **n = &head_;
for (Node *ptr = list.head_; ptr; ptr = ptr->next_) {
*n = new Node(ptr->value_, tail_, nullptr);
if (tail_) tail_->next_ = *n;
tail_ = *n;
n = &(tail_->next_);
}
}
Also, although your move assignment operator will "work", it is generally not a good idea to make the source object being moved to take ownership of the old data, since you don't know how the caller is using the source object. Just because you take an rvalue reference as input does not ensure the caller is passing in an actual temporary object 1 (case in point, your own example does not).
1: Though, the general rule of thumb is to never use an object after it is moved from, other than to reassign or reset it.
A safer option, which will also allow you to support copy assignment and move assignment in a single implementation, is to take the input parameter by value. This way, any assignment will construct a temp object utilizing your existing move constructor or copy constructor as needed, thus leaving the source object in a valid moved-from or copied-from state, and then you swap ownership of data with your temp object:
List& operator= (List list) {
std::swap(head_, list.head_);
std::swap(tail_, list.tail_);
return *this;
}
I have some linked list code and everything is working until I create my pop_front function in my destructor. The code works everwhere else and I've tried printing the linked list to see if it was created properly and it was. This is the only part of my code where pop_front as been called and the only part where I am deallocating anything
template <typename T>
class DList {
Node<T>* head;
Node<T>* tail;
public:
DList() {
head = nullptr;
tail = nullptr;
}
void push_front(T newData) {
Node<T>* newNode = new Node<T>(newData, head, nullptr);
if (head) {
head->prev = newNode;
}
else {
tail = newNode;
};
head = newNode;
}
void push_front(DList<T> newList) {
newList.tail->next = head;
head->prev = newList.tail;
head = newList.head;
}
void pop_front() {
if (head) {
Node<T>* pop = head;
head = pop->next;
if (head) {
head->prev = nullptr;
}
else {
tail = nullptr;
}
delete pop;
}
}
~DList() {
while (head) {
pop_front();
}
}
};
Your DList class (and possibly also the template class Node as well, we can't see its definition) does not follow the rule of three/five. You define a destructor but you don't define a copy constructor or copy assignment operator.
This means that making a copy of a DList at all (which calling DList::push_front(DList<T>) almost certainly will) will cause a use-after-free in the destructor.
Remember that compiler-generated copy constructors and copy assignment operators simply perform a memberwise copy of values. In this case, your values are pointers. Therefore, a copy of a DList object will simply copy the original pointers to the new object.
When one of those objects is destructed, the list will be torn down, however the other object will still be holding pointers to the memory allocations that were freed.
You must implement the copy constructor and copy assignment operator if you wish your type to be copyable, because the compiler-generated versions will do the wrong thing. If you are using C++11, you should also define the move constructor and move assignment operator.
In C++11, you could also delete these constructors and operators and then you'll get a compile-time error everywhere that you attempt to make a copy:
DList(DList const &) = delete;
DList(DList &&) = delete;
DList & operator=(DList const &) = delete;
DList & operator=(DList &&) = delete;
Without the definition of the Node template class I cannot suggest an implementation of the copy constructor or copy assignment operator. However, I can suggest an implementation for the move variants:
void swap(DList & other) {
std::swap(head, other.head);
std::swap(tail, other.tail);
}
DList(DList && other) : head(nullptr), tail(nullptr) {
swap(other);
}
DList & operator=(DList && other) {
swap(other);
return *this;
}
Another error:
void push_front(DList<T> newList)
Should be:
void push_front(DList<T> & newList)
Because you modify the "new list" -- it doesn't make much sense to modify a copy. However, the semantic meaning of this doesn't match push_front(T)!
The single-value overload takes a value and pushes it onto this list.
The list overload takes another list and pushes this list onto the other list. It would make much more sense for the list overload to push the other list onto this list as it would mirror the behavior of the single-value overload ("add this thing to this list, whatever that means").
The implementation of this method is also flawed, as it doesn't set either list's pointers to null, which means that you are setting up another use-after-free.
I am having trouble coding Copy constructor for C++ HashTable. Now below is the class structure
template <class TYPE>
class HashTable : public Table<TYPE>
{
struct Record
{
TYPE data_;
string key_;
Record* Next;
Record(const string& key, const TYPE& data)
{
key_ = key;
data_ = data;
Next = nullptr;
}
Record(const Record& a) {
if(!a.key_.empty()){
if(a.Next == nullptr){
Next = nullptr;
}
else
{
Record* temp = a.Next ;
Record *temp2 = Next;
while(temp != nullptr)
{
temp2 = temp ;
temp = temp->Next ;
}
temp2->Next = nullptr;
}
data_ = a.data_ ;
key_ = a.data_ ;
} // user-
};
int TableSize;
Record** records;
}
};
and below is the copy constructor
template
HashTable<TYPE>::HashTable(const HashTable<TYPE>& other)
{
records = new Record*[other.TableSize];
TableSize = other.TableSize;
for(int i = 0 ; i < other.TableSize; i++)
records[i]= (new Record(*other.records[i]));
}
I have also posted the code on ideone http://ideone.com/PocMTD. The code for copy constructor seems to be crashing. I don't see any memory leak that will cause the program to crash. I have tried memcopy, using the insert function and the all seems to fail.
Replace int TableSize; and Record** records; with std::vector<std::unique_ptr<Record>>
In Record, change Record* Next; to Record* Next=nullptr;.
Stop calling new.
Include HashTable(HashTable&&)=default;.
HashTable<TYPE>::HashTable(const HashTable<TYPE>& other)
{
records.reserve( other.records.size() );
for (auto const& rec_in : other.records)
records.emplace_back( new Record(*rec_in) ); // make_shared<Record> in C++14
}
Now we are no longer doing manual memory management. So an entire set of worries is gone.
Next, look at that raw Next pointer. It is bad news. When you copy a Record, the Next pointer points into the old set of Record structures.
We can fix this in a few ways. The slickest is to use an offset pointer.
template<class T>
struct offset_ptr {
std::ptrdiff_t offset = std::numeric_limits<std::ptrdiff_t>::max();
explicit operator bool()const {
return offset!=std::numeric_limits<std::ptrdiff_t>::max();
}
T* get() const {
return (T*)( offset+(char*)this );
}
T* operator->() const { return get(); }
T& operator*() const { return *get(); }
operator T*() const { return get(); }
offset_ptr(std::nullptr_t):offset_ptr() {}
explicit offset_ptr(T* p) {
if (!p) return;
offset = (char*)p-(char*)this;
Assert(*this);
}
offset_ptr()=default;
offset_ptr(offset_ptr const&)=default;
offset_ptr& operator=(offset_ptr const&)=default;
offset_ptr(offset_ptr&&)=default;
offset_ptr& operator=(offset_ptr&&)=default;
};
which instead of storing a pointer by absolute location, stores an offset.
Now we do this:
template<class TYPE> struct Table{};
template <class TYPE>
class HashTable :public Table<TYPE>
{
public:
struct Record
{
TYPE data_;
std::string key_;
offset_ptr<Record> Next;
Record(const std::string& key, const TYPE& data)
{
key_ = key;
data_ = data;
Next = nullptr;
}
Record(const Record& a)
{
if(!a.key_.empty())
{
if(a.Next == nullptr)
{
Next = nullptr;
}
else
{
auto temp = a.Next;
while(temp != nullptr)
{
Next = temp;
temp = temp->Next;
}
}
data_ = a.data_;
key_ = a.data_;
}
}
};
std::vector<Record> records;
};
and no copy ctor is needed; the offset ptr knows the location of the other record as an offset within the records. Data is stored by-value instead of by-reference.
Note that we have a vector of Records, not pointers-to-Records. This is key for the offset_ptr to work. Resizing isn't a problem, as the offsets remain the same. Copying remains safe, as offsets on each side now refer to other elements within their vector. Inserting/removing in the middle is dangerous, but simply nulling elements is not.
Note that buffers of size max std::ptrdiff_t or beyond are not supported by the above offset_ptr. On a 64 bit system that is about 2 gigs; on a 64 bit system it is large. (I don't use 0 for the null value, because if I did then an offset_ptr<X> as the first member of a struct X would nullify if I ever made it point to its enclosing X.)
boost also has a less bespoke offset_ptr type. The implementation above is meant as a sketch of how easy it is, not a solid implementation.
You do not show the complete code here (neither on ideone), but let me take a guess based on what I see.
I assume that your other object, which you pass in the copy c'tor has a fully set up list of Records.
I further assume that your HashTable class has a destructor (not shown) which deletes all the linked Records.
Your copy constructor calls the copy c'tor of Record(for each entry in the array of pointers to Record). The Record coyp c'tor only makes a shallow copy, i.e. only the pointer to the next element is copied (it will still point to the next element of the copied Record from the other hash table.
Thus, when other and its copy are deleted (at the end of scope or program; not shown), you will have double deletion (crash).
Fix: Make sure that Record has correct copy constructor, copy assignment and destructor (maybe even move c'tor and move assignment) (rule of five).
The same applies for the HashTable class as well.
Better fix: Use std::unordered_map.
Let's say I have a List class:
class List
{
private:
class Node{
public:
int data;
Node* next;
public:
virtual ~Node()
{
if (next != NULL)
delete next;
}
};
Node* head;
public:
virtual ~List()
{
if (head != NULL)
{
delete head;
}
}
public:
void AddNode(int data);
void DeleteNode(int data);
//....
};
Now I want to implement a function, which takes two List reference as arguments and return a new created List:
List SumTwoList(List& list_1, List& list_2)
{
//here I create a new List list_3 and add some elements to it based on list_1 and list_2(add operation use dynamic allocation).
//finally I want to return this list_3.
return list_3;
}
I am confusing here. How should I create this list_3 inside SumTwoList(). If I just make it a local variable, when function returns, the destructor of list_3 will free all the nodes added to the list and destroy the whole list. However, if I do dynamic allocation to create this list_3 and return a pointer to it, it is the user's responsibility to delete this list after using.
I don't know how to choose from these two ideas and I'm pretty sure there are some much better methods to solve this problem. Thanks ahead for your advice. :-)
Add a copy constructor and assignment operator (and fix your destructor):
class List
{
private:
typedef struct node{
int data;
struct node* next;
}NODE;
NODE* head;
public:
virtual ~List() // Please fix this, as others have mentioned!
{ }
List(const List& rhs)
{
// this needs to be implemented
}
List& operator = (const List& rhs)
{
// this needs to be implemented
}
//...
};
The "this needs to be implemented" is what you need to fill in to get the copies to work.
So what does this buy you? First, you can now write functions in the form that you were attempting to do in your question. That is, you can return a List by value safely and without doing any further coding to dynamically allocate or delete a list.
List SumTwoList(List& list_1, List& list_2)
{
List list_3;
// do stuff to add data to list_3...
//...
//finally I want to return this list_3.
return list_3;
}
Second, say we want to copy a list to another list. That can be done easily using the "=" or by simple copy construction
If you don't want to copy, then turn copying off by making copy-ctor and assignment op private and unimplemented*, otherwise copying will be an option to the programmer using your class and the compiler will also make copies when it needs to.
In other words, if copying is implemented correctly, then a simple program like this should work:
int main()
{
List lis1;
// Add some nodes to lis1...
//...
// Assume that lis1 now has nodes...complete the copying test
List lis2(lis1);
List lis3;
lis3 = lis1;
}
The program above should have no memory leaks, and no crashes, even when main() returns. If not, then copying and/or destruction is broken.
*C++11 allows you to make the copy constructor and assignment operator disabled in an easier way than with pre C++11 code by using the delete keyword when defining the functions.
One option is to return a pointer to a new List
List * SumTwoList(List& list_1, List& list_2)
{
List * pResultList = new List;
// Iterate over all elements of list_1 and add (append) them to pResultList
// Iterate over all elements of list_2 and add (append) them to pResultList
return pResultList;
}
However, it has the drawback that the caller have to remember to delete the returned object.
Rather, it might be better to design the API slightly differently where elements of list_2 are append to the existing list, for example
// Append contents of rhs to this. rhs stays as is
List::append( List const & rhs );
Callers may call it as
List list_1;
// work on list_1, such as add elements
List list_2;
// work on list_2, such as add elements
list_1.append( list_2 );
This is close to std::list::splice(), but not entirely. splice() moves the elements from rhs to this, but append() just copies the elements.
Note, your destructor is buggy, it is delete'ing only thehead, the later elements are leaked.
A sketch for destructor implementation
List::~List() {
while( head ) {
struct node * oldHead = head;
head = head->next;
delete oldHead;
}
head = NULL;
}
You will return a copy of a list. This will do what you expect if copy constructor and assignment operator of class List will be implemented.
class List
{
private:
//...
public:
List();
List( const List& other);
List& operator= ( const List& other);
//...
};
Maybe better option would be however to create a constructor which takes two Lists and constructs a new one by merging them ( doing all this what SumTwoList would do):
class List
{
private:
//...
public:
List( const List& first, const List& second) { // similar to SumTwoList
}
//...
};
First, I realize that there are multiple topics about this on StackOverflow. I'm having a slight bit of trouble understanding the responses in some of them. I'm creating this in hopes that someone can help me understand the process.
I apologize if this question seems relatively novice, but I am doing my best to understand it.
I'm learning about data structures and have been asked to create a LinkedList implementation file based on a header that was provided.
This is homework, so please no 'here is the exact code' type answers. Pseudocode, steps, and hints are welcome.
Here is the part of the header that I'm working on at the moment:
typedef std::string ElementType;
class LinkedList
{
private:
class Node
{
public:
ElementType data;
Node * next;
Node( )
: data( ElementType( ) ), next( NULL )
{ }
Node( ElementType initData )
: data( initData ), next( NULL )
{ }
}; // end of Node class
typedef Node * NodePointer;
public:
LinkedList( );
/* Construct a List object
Precondition: none.
Postcondition: An empty List object has been constructed.
*/
LinkedList( const LinkedList &source );
/* Construct a copy of a List object.
Precondition: None.
Postcondition: A copy of source has been constructed.
*/
~LinkedList( );
/* Destroys a List object.
Precondition: None.
Postcondition: Any memory allocated to the List object has been freed.
*/
const LinkedList & operator=( const LinkedList &rightSide );
/* Assign a copy of a List object to the current object.
private:
NodePointer first;
int mySize;
};
So far, I've created the destructor, can you check and make sure it is correct?
//Destructor
LinkedList::~LinkedList()
{
NodePointer ptr = first;
while(ptr != 0 ) {
NodePointer next = ptr->next;
delete ptr;
ptr = next;
}
first = 0;
}
Now here is the part where I'm lost...What are the basic steps of creating the copy constructor? I've finished the default constructor which was simple, but I'm a bit confused with what I should be doing on the copy constructor.
I'm also slightly confused about overloading the = operator, I assume it will be very similar to the copy constructor.
Edit
My first attempt at the copy constructor:
LinkedList::LinkedList(const LinkedList & source)
{
//create a ptr to our copy
Node * copy_node = source.first;
//where we will be inserting our copy
Node * insert_node = first;
while(copy_node != nullptr)
{
//insert our new copy node
insert_node = new Node(copy_node->data);
//move to our next node
copy_node = copy_node->next;
if(copy_node != nullptr) {
insert_node = insert_node->next;
} else {
insert_node = first;
}
//update size
mySize++;
}
}
I feel like something is missing there.
What are the basic steps of creating the copy constructor? I've finished the default constructor which was simple, but I'm a bit confused with what I should be doing on the copy constructor.
Well, you need to copy the source, obviously. If the source is a list of N nodes then you need to construct another list of N nodes, with each node being a copy of the corresponding one in the source.
So loop over the source nodes and create copies of them.
I'm also slightly confused about overloading the = operator, I assume it will be very similar to the copy constructor.
Yes, except you need to dispose of the current nodes first. However, a simple and safe way to implement assignment is copy-and-swap, so define a correct swap member:
void swap(LinkedList& other)
{
std::swap(first, other.first);
std::swap(size, other.size);
}
Then use it to implement assignment:
LinkedList& operator=(const LinkedList& source)
{
LinkedList(source).swap(*this);
return *this;
}
This creates a temporary that is a copy of source, then swaps it with *this, so the old contents of *this get destroyed by the temporary, and *this ends up with the copied data.
N.B. the return type should be non-const, it is not idiomatic to return a const-reference from an assignment operator.
The most easiest way is to implement a function which adds new nodes into the list and call it in the loop inside the constructor:
LinkedList(const LinkedList& rhs)
{
Node* node = rhs.first;
while (node) {
add(node.data);
node = node->next;
}
}
void add(ElementType data)
{
Node* node = new Node(data);
// add node somehow
// I'd recommend to keep pointer to the tail
}
Please note this implementation is not exception safe!
EDIT: added copy function for the copy constructor, the first step to the exception safety:
LinkedList(const LinkedList& rhs)
{
copy(rhs.first, first);
}
void copy(Node* src, Node*& dest)
{
// handle the head element separately
if (!src) {
return; // empty list
} else {
dest = new Node(src->data); // equivalent to first = new Node...
src = src->next;
}
// copy the rest of the list
Node* p = dest;
while (src) {
p->next = new Node(src->data);
src = src->next;
p = p->next;
}
}