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.)
Related
Complete repository: https://github.com/mervynlee94/queue/tree/master
I have implemented LinkedList and tested my copy constructor without issue.
LinkedList<int> l1;
l1.insert(1,3); // set value 3 in 1st position
l1.insert(2,7); // set value 7 in 2nd position
l1.insert(3,9); // set value 9 in 3rd position
LinkedList<int> l2(l1); // copy constructor
l2.setEntry(2,5); // set value 5 in position 2
cout<< l1.getEntry(2) <<endl; // output: 7
cout<< l2.getEntry(2) <<endl; // output: 5
My Queue implementation uses this LinkedList module as the data structure with code below.
#ifndef _LIST_QUEUE
#define _LIST_QUEUE
#include "QueueInterface.h"
#include "LinkedList.h"
template<class ItemType>
class ListQueue : public QueueInterface<ItemType> {
private :
LinkedList<ItemType> list;
public :
ListQueue();
ListQueue(const ListQueue& aQueue);
~ListQueue();
bool isEmpty() const ;
void enqueue( const ItemType& newEntry);
void dequeue();
ItemType peekFront() const;
};
#endif
template<class ItemType>
ListQueue<ItemType>::ListQueue() {
}
template<class ItemType>
ListQueue<ItemType>::ListQueue(const ListQueue& aQueue) {
list = LinkedList<ItemType>(aQueue.list);
}
template<class ItemType>
ListQueue<ItemType>::~ListQueue() {}
template<class ItemType>
bool ListQueue<ItemType>::isEmpty() const {
return list.isEmpty();
}
template<class ItemType>
void ListQueue<ItemType>::enqueue(const ItemType& newEntry) {
list.insert(list.getLength()+1, newEntry);
}
template<class ItemType>
void ListQueue<ItemType>::dequeue() {
list.remove(1);
}
template<class ItemType>
ItemType ListQueue<ItemType>::peekFront() const {
return list.getEntry(1);
}
When I test the code out in main.cpp, I encountered segmentation error.
ListQueue<int> q1;
q1.enqueue(1);
q1.enqueue(2);
q1.enqueue(3);
ListQueue<int> q2(q1); // Set breakpoint here
q2.enqueue(5);
cout<<q2.peekFront()<<endl;
cout<<q1.peekFront()<<endl;
I set a debugging breakpoint on above and I realise one thing. The copy constructor in Queue implementation doesn't work properly.
The debugging value shown in copy constructor of aQueue.list and this.list varies.
Here is the LinkedList copy constructor implementation
template<class ItemType>
LinkedList<ItemType>::LinkedList(const LinkedList<ItemType>& aList) {
Node<ItemType>* listPtr = aList.headPtr;
if(listPtr == nullptr) {
headPtr = nullptr;
}
else {
headPtr = new Node<ItemType>(listPtr->getItem());
Node<ItemType>* newPtr = headPtr;
while(listPtr->getNext() != nullptr) {
listPtr = listPtr->getNext();
Node<ItemType>* newNode = new Node<ItemType>(listPtr->getItem());
newPtr->setNext(newNode);
newPtr = newPtr->getNext();
}
}
itemCount = aList.getLength();
}
Note that since the copy-ctor of LinkedList is already implemented, you don't have to reimplement it - C++ will do it automatically for you.
Another important thing - copy constructor and copy assignment operator are distict.
You implemented Copy-ctor for your Queue, and in this copy-ctor you used the copy-assignment operator, and I suspect you did not implement it.
The easiest way to fix it is to simply remove the copy constructor and destructor, they are not necessary. The correct way to invoke a copy-constructor is as follows:
template<class ItemType>
ListQueue<ItemType>::ListQueue(const ListQueue& aQueue)
: list(aQuquq.list) {
}
I have three more points to make:
Rule of 3 - if you declare one of copy-constructor, copy-assignment or destructor, you almost surely have to implement them all.
There is a bit more complicated feature in C++ called "move semantics", in case you are familiar with the term, you should consider the Rule of 5 - the same as the previous rule, with addition of move-constructor, and move-assignment operator.
It seems as you are using inheritance, but I don't see the keyword override anywhere. If you are using C++11 or above, consider adding the override qualifier to overriding methods.
And, lastly, C++11 supports default ctors/dtors and assignment operators. If you write an empty destructor (as you do here), the cleaner syntax is to write
class ListQueue : public QueueInterface<ItemType> {
...
~ListQueue() = default;
...
};
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'm reading lines from a file and storing them into linked list.
void add(llist *list, somevalue) {
Node *newnode = (Node *) malloc(sizeof(Node));
newnode->value = somevalue;
newnode->next = list->head;
list->head = newnode;
}
and I call this function from an initialize function which opens the file and reads lines from the file.
void init() {
llist *list = (llist *) malloc(sizeof(llist));
//
//bunch of file i/o codes
//
while (read file until it returns NULL) {
add(list, line);
//if I try to print the values of the list here it works.
}
//Outside the loop, the head is back to NULL
}
And another problem that I realized is the values get concatenated every time I try to print the value. That is to say, the output would be:
First Loop: Tony
Second Loop: Peter
TonyPeter
Third Loop: Mir
PeterMir
TonyPeterMir
How do I fix it so the add function permanently adds the node to the linked list?
Why would the values be jumbled up like that?
----EDITED----
The list is a global variable, and here are some more snippets from the init function. This is the while loop with the problem:
//open file
//initialize & declare pointers
while (1) {
for (i = 0; i < max; i++) {
value[i] = '\0';
}
if (!(fgets(value,max,f))) {
//segfaults if I try to print out the list inside this block.
break;
}
add(list, value);
//the values are well separated in this statement
printf("id is %s\n", list->head->value);
//This print list works, but works weird as shown above.
print_list(list);
}
fclose(f);
//This print list doesn't work, the list is NULL
print_list(list);
And this is the print list function:
void print_users(llist *list) {
ListNode *e;
if (list->head == NULL) {
printf("NO USERS\r\n");
return;
}
e = list->head;
while (e != NULL) {
puts(e->id);
e = e->next;
}
}
So I don't have a good grasp at all on what you're exactly trying to do here, so we can only do but so much. You may consider posting a MCVE. However, I may be able to give you some pointers on building a linked list. I directly copied your add function into a linked list class that I just hurriedly built, and it worked fine, so there may be something else in your llist class that is causing the issue, or it could be something else in your code. The class and a brief description of the class are listed below.
basic node class
Note: I used templates, but you could just as easily remove the template statements and replace T with any type.
template<typename T>
class node {
private:
T data;
node* next;
public:
// The C++11 rule of 5
// Default Constructor
node(T value = T(), node* nxt = nullptr) : data(value), next(nxt) { }
// Copy Constructor
node(const node<T>& src) : data(src.data), next(src.next) { }
// Move Constructor
node(node<T>&& src) : data(src.data), next(src.next) {
src.data = T();
src.next = nullptr;
}
// Copy Assignment Operator
node<T>& operator=(const node<T>& src) {
this->data = src.data;
this->next = src.next;
return(*this);
}
// Move Assignment Operator
node<T>& operator=(node<T>&& src) {
this->data = src.data;
this->next = src.next;
src.data = T();
src.next = nullptr;
}
// Destructor
~node() {};
// Some functions to help with encapsulation
void set_next(node* nxt) {
this->next = nxt;
}
void set_data(T value) {
this->data = value;
}
node* get_next() {
return(this->next);
}
T& get_data() {
return(data);
}
};
linked list class body
Since you're using dynamic memory, you need to make sure you adhere to the rule of 3 or 5 depending on whether or not you're using C++11.
template<typename T>
class llist {
private:
node<T>* head;
public:
llist();
llist(const llist& src);
llist(llist&& src);
llist(~llist);
llist& operator=(const llist& src);
llist& operator=(llist&& src);
void push();
void insert();
};
default constructor
Nothing fancy here.
template<typename T>
llist<T>::llist() : head(nullptr) { }
copy constructor
Since you're using dynamic memory this is crucial
template<typename T>
llist<T>::llist(const llist& src) {
node<T>* tmp = src.head;
while(tmp) {
this->push(tmp->get_data());
}
}
move constructor
template<typename T>
llist<T>::llist(llist&& src) {
// delete current nodes
node<T>* tmp = this->head;
while(tmp) {
tmp = head->get_next();
delete head;
head = tmp;
}
// steal the sources list
this->head = src.head;
src.head = nullptr;
}
destructor
template<typename T>
llist<T>::~llist() {
node<T>* tmp = this->head;
while(tmp) {
tmp = head->get_next();
delete head;
head = tmp;
}
}
copy assignment operator
template<typename T>
llist& llist<T>::operator=(const llist<T>& src) {
node<T>* tmp = src.head;
while(tmp) {
this->push(tmp->get_data());
}
return(*this);
}
move assignment operator
template<typename T>
llist& llist<T>::operator=(llist<T>&& src) {
node<T>* tmp = this->head;
while(tmp) {
tmp = head->get_next();
delete head;
head = tmp;
}
this->head = src.head;
src.head = nullptr;
return(*this);
}
push member
this is essentially opposite of your add member.
template<typename T>
void llist<T>push(T data) {
node<T>* new_node = new node<T>(data);
if(this->head) {
node<T>* tmp = this->head;
while(tmp->get_next()) {
tmp = tmp->get_next();
}
tmp->set_next(new_node);
} else {
this->head = new_node;
}
}
insert member
This is essentially your add member.
template<typename T>
void llist<T>insert(T data) {
node<T>* new_node = new node<T>(data, this->head);
this->head = new_node;
}
I don't know if this will help, and you probably already have and know most of this, but I hope it helps.
In this code, it would appear that you attempted to 'malloc' space for a "llist" user defined object.
void init() {
llist *list = (llist *) malloc(sizeof(llist));
//
//bunch of file i/o codes
//
while (read file until it returns NULL) {
add(list, line);
//if I try to print the values of the list here it works.
}
//Outside the loop, the head is back to NULL
}
First, you tagged this as C++. In C++, you simply must use new and delete. The C++ compiler does not associate "malloc" with the ctor / dtor of your user created object called "llist". And I assure you that you really do want to create these two methods, even when each are simple. Really.
On the other hand, the C++ compiler does provide New and Delete, and will automatically invoke the ctor and dtor when appropriate for both dynamic variables (in heap), and automatic variables (on stack). The compiler will not support this with malloc.
Second, your function init() does not return or otherwise deliver the value of the automatic variable you named "list" to any other scope. (Typically, a list lifetime exceeds the life of any function that uses or creates it.)
So your object "list" only exists within the scope of the function init(), within the lifetime of init(). Not very useful.
So the handle to the list of 'malloc'ed things is lost, no longer accessible to anything else. After init(), where did you plan for the listHead to reside?
Even if you used new (and delete) the code still does not deliver the listHead anywhere.
To further your program, you need perhaps 1 of 2 things:
1) a return (from the function) of the "list" handle (I've been calling it "listHead" as you intended, right?)
llist* init() {
llist *listHead = ...
return(listHead);
}
OR
2) a parameter reference which your init function changes. This places the list head outside of init().
void init( llist** listHead) {
llist *list = ...
*listHead = list;
}
You might look into, and take hints from std::list, which has 40+ methods, though you might only need 10. For the methods you plan to implement, you should strive to conform to and use similar names and parameters.
Perhaps you meant to use a class data attribute with the label list (it is quite difficult to imagine this from what you provided). In this case, you should distinguish data attributes names to help you remember what it is, and that it has a different scope. For instance, I would use m_listHead. The prefix m_ (or often, simply the one char prefix '_') simply indicates to the reader that this symbol is a data attribute of a class. This idea is a common c++ idiom, and not enforced by the compiler, but is often part of a coding-standard.
Good luck.
I am trying to make a queue implementing a linked list but am running into a compiler error. The error is coming from the overloaded assignment operator function on the line where I call the destructor (marked with an all-caps comment). I have a hunch it is a simple fix that has something to do with the syntax of my constructor/destructor declarations.
The error I am getting states the following code: error C2512: 'Queue<char>::Queue' : no appropriate default constructor available
It mentions no constructor, but the line it refers to is the one below where I am trying to call the destructor.
Thanks in advance for your help.
#ifndef QUEUE_H
#define QUEUE_H
#include <iostream>
using namespace std;
template <class Type>
class Queue // Create a Queue data structure implementing a linked list
{
private: // The private members
struct Cell // The Cell class will be the blueprints for each link in the list
{
Type data; // The information held by the cell
Cell* next; // The link to the next cell
};
Cell* first = NULL;
Cell* last = NULL;
public: // The public members
Queue(Type);
bool isEmpty();
void push(Type);
Type pop();
Queue<Type>& operator=(Queue<Type>&);
friend ostream& operator<<(ostream&, const Queue<Type>&);
~Queue();
};
template<class Type>
Queue<Type>::Queue(Type inputData) // Constructor that initializes the queue with a new cell that last and first point to
{
first = new Cell;
first->data = inputData;
first->next = NULL;
last = first;
}
template<class Type>
Queue<Type>& Queue<Type>::operator=(Queue<Type>& queue) // Overload "=" so that it performs a deep copy of a Queue object
{
if (!queue.isEmpty())
{
~Queue(); // HERE IS THE ERROR LINE
Cell* rhs = queue.first;
while (rhs != NULL)
{
push(rhs->data);
rhs = rhs->next;
}
}
return *this;
}
template<class Type>
Queue<Type>::~Queue() // Destructor that deallocates all of the memory used by the queue.
{
if (!isEmpty()) // We only need to deallocate the queue if it is non-empty
{
Cell *link = last;
while (link != NULL) // Until we reach the end of the queue, keep deleting each link
{
pop();
}
first = NULL;
last = NULL;
}
else // If the queue is already empty, let the user know
{
cout << "Cannot call destructor. The list is already empty.\n";
}
}
#endif
Check out this thread: Can i call destructor from its class method?. An easy way around this is to make a function to empty the queue, then call it from the destructor and assignment operator.
template<class Type>
void Queue<Type> empty(){
if (!isEmpty()) // We only need to deallocate the queue if it is non-empty
{
Cell *link = last;
while (link != NULL) // Until we reach the end of the queue, keep deleting each link
{
pop();
}
first = NULL;
last = NULL;
}
else // If the queue is already empty, let the user know
{
cout << "Cannot call empty. The list is already empty.\n";
}
}
template<class Type>
Queue<Type>& Queue<Type>::operator=(Queue<Type>& queue) // Overload "=" so that it performs a deep copy of a Queue object
{
if (!queue.isEmpty())
{
empty(); // Tada, no more error
Cell* rhs = queue.first;
while (rhs != NULL)
{
push(rhs->data);
rhs = rhs->next;
}
}
return *this;
}
template<class Type>
Queue<Type>::~Queue() // Deconstructor that deallocates all of the memory used by the queue.
{
empty();
}
This has nothing to do with template.
If you declare any constructor for your class, the compiler synthesized default constructor(i.e. the one that takes no arg) is deleted.
You have to define Queue() yourself.
BTW, a using directive in the global scope is not a good idea.
I guess you define a queue without parameter, like
Queue<char> quCh;
If you want to do this, you must define a constructor without parameter.
Queue();
or you must define your queue like this:
Queue<char> quCh('a');
I have one semestral work (own double linked list) and our teacher want this definition of class DoubleList:
template <typename T> //just part of all methods
class DoubleList {
public:
DoubleList(void); //We HAVE TO follow this definitions
void AddFirst(const T &); //const!
T &AccessActual(void);
T RemoveFirst(void);
}
My question is, how can I define a node? AddFirst have const argument and other methods haven't. Data must be set in constructor and then they can't be changed. Is this task so limited or are here other ways to complete the task?
Here is my actual Node:
template <class U>
class Node{
Node<U> * next;
Node<U> * previous;
const U * data;
public:
Node(const U *data){ //
next = NULL;
previous = NULL;
this->data = data;
}
void SetNext(Node<U> *next) {
this->next = next;
}
Node<U> *GetNext(){ return next; }
void SetPrevious(Node<U> *previous) {
this->previous = previous;
}
Node<U> *GetPrevious(){ return previous; }
const U *GetData() { return data; }
};
In containers, it's usually better to have a copy of the data so change const U * data; to U data;
The Node constructor would be easier to use if it had this signature Node(const U& data). No pointers.
The GetData would also have to change. Return a reference. U& GetData().
It is dangerous to hold addresses of data items. If the user of the lists wants that functionality he can use a list that stored pointers (e.g. U=int*)
Your node class seems fine, although i would keep using template argument T instead of U, right now it is confusing.
Your AddFirst() method should simply create a new node and assign the correct next pointer to the new node and the correct prev pointer to the "old" first node and adjust the actual object? what does that refer to?
Your interface of nodes differs from this one returning a reference instead of a pointer. I find it quite strange that the AccessActual can always return an object, while when the list is empty this can be a nullptr??
example implementation:
void AddFirst(const T &)
{
Node<T>* newNode = new Node<T>(T);
Node<T>* current = &AccessActual(); // how can there be an actual when the list can be empty or is that impossible?
{
while( current.GetPrev() != nullptr )
{
current = *current.GetPrev();
}
current.SetPrev(newnode);
newnode->SetNext(current);
}
}