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.
Related
I'm having trouble switching this linked stack file into using only smart pointers. I've tried some things and I just get loads of errors. Any help is appreciated. This is in C++ by the way.
There is supposed to be no raw pointers, no constructors not needed, and use of the auto keyword whenever possible.
Here's the code:
#include <new>
#include <string>
#include "PrecondViolatedExcep.h"
template <typename ItemType>
LinkedStack<ItemType>::LinkedStack(const LinkedStack<ItemType>& aStack) {
if (!aStack.topPtr) {
topPtr = nullptr;
}
else {
NodePtr origStackPtr(aStack.topPtr);
try {
topPtr = new Node<ItemType>(origStackPtr->getItem() );
NodePtr newStackPtr(topPtr);
origStackPtr = origStackPtr->getNext();
while (origStackPtr) {
newStackPtr->setNext(
new Node<ItemType>(origStackPtr->getItem())
);
newStackPtr = newStackPtr->getNext();
origStackPtr = origStackPtr->getNext();
}
}
catch (const std::bad_alloc&) {
while (!isEmpty() ) {
pop();
}
throw;
}
}
}
template <typename ItemType>
LinkedStack<ItemType>::~LinkedStack() {
while (!isEmpty() ) {
pop();
}
}
template <typename ItemType>
bool LinkedStack<ItemType>::isEmpty() const {
return !topPtr;
}
template <typename ItemType>
bool LinkedStack<ItemType>::push(const ItemType& newItem) {
try {
topPtr = new Node<ItemType>(newItem,
topPtr);
}
catch (const std::bad_alloc&) {
return false;
}
return true;
}
template <typename ItemType>
bool LinkedStack<ItemType>::pop() {
if(!isEmpty() ) {
NodePtr nodeToDeletePtr(topPtr);
topPtr = topPtr->getNext();
delete nodeToDeletePtr;
return true;
}
return false;
}
template <typename ItemType>
ItemType LinkedStack<ItemType>::peek() const {
// throw(PrecondViolatedExcep) {
if (isEmpty() ) {
std::string message("LinkedStack::peek() ");
message += "called on an empty stack.";
throw PrecondViolatedExcep(message);
}
return topPtr->getItem();
}
I hope the commentary on each piece of the answer demonstrates the principles well enough you can learn more than just these specific solutions.
Though you don't show the definitions of the class templates, it looks like LinkedStack<ItemType> has a private member topPtr, and you'd like to change its type from Node<ItemType>* to std::unique_ptr<Node<ItemType>>. Also, class template Node has member functions something like:
template <class ItemType>
class Node
{
// ...
// Either public, or Node befriends LinkedStack in some way.
explicit Node(const ItemType& value, Node* next = nullptr);
Node* getNext() const { return nextPtr; }
void setNext(Node* ptr) { nextPtr = ptr; }
// ...
// Probably private:
Node* nextPtr;
};
Changes to this Node template could look like:
template <class ItemType>
class Node
{
// ...
// Either public, or Node befriends LinkedStack in some way.
explicit Node(const ItemType& value)
Node(const ItemType& value, std::unique_ptr<Node> next)
: Node(value) { nextPtr = std::move(next); }
Node* getNext() const { return nextPtr.get(); }
void setNext(std::unique_ptr<Node> ptr) { nextPtr = std::move(ptr); }
void resetNext() { nextPtr = nullptr; }
[[nodiscard]] std::unique_ptr<Node> releaseNext()
{ return std::move(nextPtr); }
// ...
// Probably private:
std::unique_ptr<Node> nextPtr;
};
The member becomes a smart pointer because the Node "owns" the responsibility for cleaning up its "next" node, if any.
The pointer parameters of the two-argument constructor and setNext become smart pointers because calling each means the Node will take over responsibility for cleaning up that next Node. They need to use std::move to allow the member to actually take that responsibility from the parameter.
You might ask, shouldn't getNext return a smart pointer? No, because that would mean that calling getNext transfers responsibility for cleaning up the next node to the caller, and that's usually not what getNext is for. A raw pointer can still have its place, meaning a pointer to an object whose ownership is handled by something else, or possibly a null pointer. (When null isn't a possibility, we'd often consider changing from a raw pointer to a reference, which also implies ownership is handled by something else.)
Though for cases where we do want to take ownership back from a Node, I've added releaseNext(). It returns a unique_ptr to "give away" its responsibility, and in the process its own unique_ptr member becomes empty.
Finally, I've added resetNext as a way to reset the nextPtr back to null, more straightforward than passing an empty smart pointer to setNext. (And/or, we could overload a setNext(std::nullptr_t).)
Then Node can follow the Rule of Zero: It does not need to declare a destructor, copy constructor, move constructor, or any sort of assignment operator. Since it has a member of type std::unique_ptr<Node>, its implicitly-declared copy constructor and copy assignment members will be defined as deleted, meaning the compiler will complain about any code that tries to use them. But it will have working move constructor and move assignment operator, which are probably not needed but harmless.
Now to class template LinkedStack. It can't use the Rule of Zero since copying it should be allowed despite the unique_ptr member and should do a deep copy. So we'll go with the Rule of Five, as modified by the Copy and Swap Idiom:
template <class ItemType>
class LinkedStack
{
public:
LinkedStack() = default;
LinkedStack(const LinkedStack&);
LinkedStack(LinkedStack&&) = default;
LinkedStack& operator=(LinkedStack) noexcept;
~LinkedStack() = default;
friend void swap(LinkedStack& s1, LinkedStack& s2) {
using std::swap;
swap(s1.topPtr, s2.topPtr);
}
// ...
private:
std::unique_ptr<Node<ItemType>> topPtr;
};
The destructor is okay to default, so with the = default; above, you can delete your custom definition.
Assignment is defined per Copy And Swap:
template <class ItemType>
LinkedStack<ItemType>& LinkedStack<ItemType>::operator=(
LinkedStack rhs) noexcept
{
swap(*this, rhs);
return *this;
}
The copy constructor gets simpler:
template <typename ItemType>
LinkedStack<ItemType>::LinkedStack(const LinkedStack<ItemType>& aStack)
: topPtr() // initially null
{
if (aStack.topPtr) {
Node* origStackPtr = aStack.get();
topPtr = std::make_unique<ItemType>(origStackPtr->getItem());
Node* newStackPtr(topPtr.get());
origStackPtr = origStackPtr->getNext();
while (origStackPtr) {
newStackPtr->setNext(
std::make_unique<ItemType>(origStackPtr->getItem())
);
newStackPtr = newStackPtr->getNext();
origStackPtr = origStackPtr->getNext();
}
}
}
The loop uses raw Node* pointers because once the nodes are safely stored in the topPtr or in another Node, the constructor code no longer needs to worry about deleting them. But it does need to reassign the variables as the loop executes, so references won't do, and it also needs to detect when origStackPtr->getNext() returns a null pointer.
Your original needed the try-catch-rethrow because in case of an exception in a constructor body, the destructor for that class is not called. But destructors of its members and base classes are called. So now if an exception happens in the copy constructor body, the destructor for the unique_ptr member topPtr executes, which will take care of deleting the top Node. Destruction of that Node's unique_ptr member will likewise delete its next node if any, and so on recursively - all with zero lines of (your) code.
isEmpty does not need any change: the expression !topPtr also works with a unique_ptr, meaning "is not null".
A straightforward update of push just changes the new to a make_unique:
template <typename ItemType>
bool LinkedStack<ItemType>::push(const ItemType& newItem) {
try {
topPtr = std::make_unique<Node<ItemType>>(
newItem, topPtr);
}
catch (const std::bad_alloc&) {
return false;
}
return true;
}
However, I'd recommend getting rid of the try/catch and return value. A memory error is rare, so most code shouldn't be checking to see whether this push called one. Something that really does care ought to do a try/catch itself around whatever amount of code is most appropriate. This would reduce the body to just one statement.
And pop gets simpler:
template <typename ItemType>
bool LinkedStack<ItemType>::pop() {
if (!isEmpty()) {
topPtr = topPtr->releaseNext();
return true;
}
return false;
}
Notice in the topPtr reassignment statement, first releaseNext() is used to take responsibility for the second Node (if any) away from the top Node. Then that responsibility is immediately given to the topPtr variable by the assignment. This also means topPtr will delete what it previously pointed at, the top node being popped off. Since that top node no longer has a next node, nothing else gets deleted, and the stack ends up in the correct state just like in the old version.
peek() does not require any change. The topPtr->getItem() expression will work via the unique_ptr<T>::operator-> function. (But I'd suggest changing the return types of Node<ItemType>::getItem() and LinkedStack<ItemType>::peek() to const ItemType&, to avoid useless copies when ItemType is a class type.)
I am having an issue in regards with my linkedlist implementation, where I am trying to create a copy constructor.
// Copy Constructor
List342(const List342& source)
{
*this = source;
}
List342& operator=(const List342& source)
{
Node<T>* s_node = nullptr; // Source node
Node<T>* d_node = nullptr; // Destination node
if (this == &source)
{
return *this;
}
// Empty memory on destination
DeleteList();
// If the source is empty, return the current object.
if (source.head_ == nullptr)
{
return *this;
}
// Copy source node to destination node, then make destination node the head.
d_node = new Node<T>;
d_node->data = (source.head_)->data;
head_ = d_node;
s_node = (source.head_)->next;
// Loop and copy the nodes from source
while (s_node != nullptr)
{
d_node->next = new Node<T>;
d_node = d_node-> next;
d_node->data = s_node->data;
s_node = s_node->next;
}
return *this;
}
For some reason, VS Studio throws a read access violation at me on the line d_node->data = s_node->data despite the while loop trying to prevent this.
The culprit may be lying in DeleteList, but for some reason my other methods, like printing the linkedlist, have no issues after calling DeleteList, as it prints nothing. I'm just wondering if there's any flaws in this DeleteList method.
// Delete all elements in the linked list
// Not only do you need to delete your nodes,
// But because the Node data is a pointer, this must be deleted too
void DeleteList()
{
// Similar to the linkedlist stack pop method except you're running a while
// loop until you the is empty.
Node<T>* temp;
while (head_ != nullptr)
{
temp = head_;
head_ = head_->next;
// For some reason if I try to delete temp->data, I keep getting symbols not loaded or debug
// assertion errors
// delete temp->data;
// What I can do here is set it to null
// Then delete it. This may have to do how uninitialized variables have random memory assigned
temp->data = nullptr;
delete temp->data;
delete temp;
}
}
Here is the Node definition:
template <class T>
struct Node
{
T* data;
//string* data;
Node* next;
}
By having Node::data be declared as a pointer, your code is responsible for following the Rule of 3/5/0 to manage the data pointers properly. But it is not doing so. Your copy assignment operator is shallow-copying the pointers themselves, not deep-copying the objects they point at.
Thus, DeleteList() crashes on the delete temp->data; statement, because you end up with multiple Nodes pointing at the same objects in memory, breaking unique ownership semantics. When one Node is destroyed, deleting its data object, any other Node that was copied from it is now left with a dangling pointer to invalid memory.
If you must use a pointer for Node::data, then you need to copy-construct every data object individually using new so DeleteList() can later delete them individually, eg:
d_node = new Node<T>;
d_node->data = new T(*(source.head_->data)); // <--
...
d_node->next = new Node<T>;
d_node = d_node->next;
d_node->data = new T(*(s_node->data)); // <--
...
However, this is no longer needed if you simply make Node::data not be a pointer in the first place:
template <class T>
struct Node
{
T data; //string data;
Node* next;
}
That being said, your copy constructor is calling your copy assignment operator, but the head_ member has not been initialized yet (unless it is and you simply didn't show that). Calling DeleteList() on an invalid list is undefined behavior. It would be safer to implement the assignment operator using the copy constructor (the so-called copy-swap idiom) , not the other way around, eg:
// Copy Constructor
List342(const List342& source)
: head_(nullptr)
{
Node<T>* s_node = source.head_;
Node<T>** d_node = &head_;
while (s_node)
{
*d_node = new Node<T>;
(*d_node)->data = new T(*(s_node->data));
// or: (*d_node)->data = s_node->data;
// if data is not a pointer anymore...
s_node = s_node->next;
d_node = &((*d_node)->next);
}
}
List342& operator=(const List342& source)
{
if (this != &source)
{
List342 temp(source);
std::swap(head_, temp.head_);
}
return *this;
}
However, the copy constructor you have shown can be made to work safely if you make sure to initialize head_ to nullptr before calling operator=, eg:
// Copy Constructor
List342(const List342& source)
: head_(nullptr) // <--
{
*this = source;
}
Or:
// Default Constructor
List342()
: head_(nullptr) // <--
{
}
// Copy Constructor
List342(const List342& source)
: List342() // <--
{
*this = source;
}
Or, initialize head_ directly in the class declaration, not in the constructor at all:
template<typename T>
class List342
{
...
private:
Node<T> *head_ = nullptr; // <--
...
};
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'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).
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
}
//...
};