0

I've been given a Node and Stack class in my .h file. I have to implement the copy constructor, assignment operator and destructor and test them in a different test file. While testing the copy constructor after inserting 3 elements its displaying only one element. I don't know what's wrong; here's my .h file for your reference:

#ifndef _STACK_H
#define _STACK_H
#include <iostream>
#include <exception>
using std::ostream;
using std::cout;
using std::endl;
using std::range_error;
// Forward declarations
template <class T> class Stack;
template <class T> class Node;
template <class T> ostream& operator<<(ostream&, const Node<T>&);

// Node class for linked list
template <class T>
class Node {
    friend Stack<T>;
public:
    Node(T data = T(), Node<T>* next = nullptr) {
        _data = data;
        _next = next;
    }
    friend ostream& operator<< <T>(ostream& sout, const Node<T>& x);
private:
    T _data;
    Node* _next;
};
// Overloaded insertion operator.  Must be overloaded for the template
// class T, or this won't work!
template <class T>
ostream& operator<<(ostream& sout, const Node<T>& x) {
    sout << "Data: " << x._data;
    return sout;
}

// Stack class.  Linked-list implementation of a stack. Uses the Node
// class.
template <class T>
class Stack {
public:
    // Constructor
    Stack();

    // Copy constructor, assignment operator, and destructor
    // DO NOT IMPLEMENT HERE.  SEE BELOW.
    Stack(const Stack& rhs);
    const Stack& operator=(const Stack& rhs);
    ~Stack();

    void push(const T& data);
    const T& top() const;
    void pop();
    bool empty() const;  // Returns 'true' if stack is empty
    void dump() const;

    //Delete method used for destructor
    void nullify();

private:
    Node<T>* _head;
    Node<T>* _temp1;
    Node<T>* _temp2; //pointers
};

template <class T>
Stack<T>::Stack() {
    _head = nullptr;
}

template <class T>
Stack<T>::Stack(const Stack<T>& rhs) {
    if (rhs._head != nullptr) {
        _head = new Node<T>(rhs._head->_data);
        _temp1 = _head->_next;  //temp1 would be the next one after head
        //_temp2 = _temp2->_next; 
        while (_temp2 != nullptr) {
            _temp1 = new Node<T>(_temp2->_data);
            _temp1 = _temp1->_next;
            _temp2 = _temp2->_next; //temp2 should be the next node after temp1
        }
    }
    else
        _head = nullptr;
}

template <class T>
const Stack<T>& Stack<T>::operator=(const Stack<T>& rhs) {
    if (this != &rhs) {
        nullify();
        if (rhs._head != nullptr) {
            _head = new Node<T>(rhs._head->_data);
            _temp1 = _head->_next;  //temp1 would be the next one after head
            //_temp2 = _temp2->_next; 

            while (_temp2 != nullptr) {
                _temp1 = new Node<T>(_temp2->_data);
                _temp1 = _temp1->_next;
                _temp2 = _temp2->_next; //temp2 should be the next node after temp1
            }
        }
        else
            _head = nullptr;
    }
    return *this;
}

template <class T>
Stack<T>::~Stack() {
    nullify();
}

template <class T>
void Stack<T>::nullify() {
    while (!empty()) {
        pop();
    }
}

template <class T>
void Stack<T>::pop() {
    if (empty()) {
        throw range_error("Stack<T>::pop(): attempt to pop from an empty stack.");
    }

    Node<T>* tmpPtr = _head->_next;
    delete _head;
    _head = tmpPtr;
}

template <class T>
bool Stack<T>::empty() const {
    return _head == nullptr;
}

template <class T>
void Stack<T>::push(const T& data) {
    Node<T>* tmpPtr = new Node<T>(data);
    tmpPtr->_next = _head;
    _head = tmpPtr;
}

template <class T>
const T& Stack<T>::top() const {
    if (empty()) {
        throw range_error("Stack<T>::top(): attempt to read empty stack.");
    }

    return _head->_data;
}

template <class T>
void Stack<T>::dump() const {
    Node<T>* nodePtr = _head;
    while (nodePtr != nullptr) {
        cout << nodePtr->_data << endl;
        nodePtr = nodePtr->_next;
    }
}
#endif

While pushing 34, 67, 92 it shows only 92 for the copy constructor during output. Here's the code for which I'm testing my .h code:

#include "stack.h"
#include <iostream>
using namespace std;
using std::cout;
using std::endl;

int main()
{
    cout << "Testing default constructor\n";

    Stack<int> intStack;

    intStack.dump();
    cout << "Stack is empty initially\n\n";

    intStack.push(34);
    intStack.push(67);
    intStack.push(92);

    cout << "Testing copy constructor after inserting 92, 67 & 34: \n";
    Stack<int> test1(intStack);
    //cout << "Dumping intStack into Test1 & displaying it: \n";
    test1.dump();

    cout << "\nTesting destructor: \n";
    test1.nullify();
    test1.dump();
    cout << "Its empty\n\n";

    Stack<int> test2;

    test2.push(75);
    test2.push(56);
    test2.push(88);
    test2.push(69);

    cout << "Testing assignment operator after inserting 69, 88, 56 & 75: \n";

    Stack<int> test3;

    test3 = test2;
    test3.dump();

    cout << "\nTesting destructor: \n";
    test2.nullify();
    test2.dump();
    cout << "Its empty\n\n";

    return 0;
}

I'm still not used to C++ completely so sorry for any errors.

ash_k123
  • 87
  • 5
  • Have you tried using a debugger? This is a simple bug that should be easily visible in any debugger, by executing the copy constructor one line at a time, and observing the values of all variables, and seeing all the logical flaws in the copy constructor. Do you know how to use a debugger? – Sam Varshavchik May 25 '20 at 23:56
  • @SamVarshavchik Yea I am using VS 2019 but still don't know how to identify it. I see red numbers while stepping in and out within each line of code. I have been tried to code this for 3 months but still when I completed it I have this copy-constructor issue :( – ash_k123 May 26 '20 at 00:14
  • `#ifndef _STACK_H` -- Do not use leading underscores for your identifiers. Names with leading underscores are reserved for the compiler implementation. Also, your copy constructor will fail to initialize all of the members if `(rhs._head != nullptr)`is false. – PaulMcKenzie May 26 '20 at 00:27
  • As a matter of fact, your default constructor fails to initialize all the members of `Stack`. You really should get into the habit of initializing everything in your objects when they are constructed. Maybe that is the cause of your issue. – PaulMcKenzie May 26 '20 at 00:34
  • Again: what did you observe when you ran the code in the copy constructor, one line at a time? If you did, and observed its attempt to copy-construct a new list, you shouldn't have any problems seeing it constructing nothing that's useful, or remotely resembles the original list being copied. Your copy constructor is wrong. It's not just one line typo or mistake, it is completely wrong. – Sam Varshavchik May 26 '20 at 02:03
  • Hi, I was only told to do the implementation of copy constructor, assignment operator and a test.cpp file to test them. I'm a business major and took this basic C++ certification course so this is like my final project. Any feedback/solution would be appreciated as I have done this based of YouTube and GeeksforGeeks due to me having zero knowledge of coding. – ash_k123 May 26 '20 at 02:16
  • @PaulMcKenzie It was already provided to me and I only had to implement for the copy constructor, assignment operator & the destructor along with a test.cpp file to test them. – ash_k123 May 26 '20 at 02:19
  • @ash_k123 *I have done this based of YouTube and GeeksforGeeks due to me having zero knowledge of coding.* -- C++ cannot be properly learned this way, and especially from sites such as "Geeks". Proper C++ books should be used, and even then it takes months or years to utilize the language properly. As to your code -- the copy constructor is wrong, it fails to initialize all the members -- the default constructor is wrong, as it also fails to initialize all the members. That `Stack` class is royally messed up in so many ways. – PaulMcKenzie May 26 '20 at 02:27

1 Answers1

2

There are several things wrong with your Stack class.

First, the copy constructor doesn't initialize all the members, and neither does your default constructor. Those need to be fixed:

template <class T>
Stack<T>::Stack() : _head(nullptr), _temp1(nullptr), _temp2(nullptr) {}

template <class T>
Stack<T>::Stack(const Stack<T>& rhs) : _head(nullptr), _temp1(nullptr), _temp2(nullptr)
{
  //...
}

Once this is done, the copy constructor can be easily implemented using your other existing function, Stack::push. Your implementation is way too complicated.

template <class T>
Stack<T>::Stack(const Stack<T>& rhs) : _head(nullptr), _temp1(nullptr), _temp2(nullptr) {
    Node<T>* temp = rhs._head;
    while (temp)
    {
        push(temp->_data);
        temp = temp->_next;
    }
}

What is being done here? Simple -- all we are doing is taking the head of the passed-in Stack, and looping over the items calling Stack::push to add the data to the new Stack object. Since you have a push function already coded, you should be using it.

Second, note that we use a local temp variable. I doubt you need any of those _temp members in your class, but that is a different story.

Last, your assignment operator can easily be implemented, given you have a copy constructor and destructor for Stack:

template <class T>
const Stack<T>& Stack<T>::operator=(const Stack<T>& rhs) {
    if (this != &rhs) {
        Stack<T> temp = rhs;
        std::swap(temp._head, _head);
        std::swap(temp._temp1, _temp1);
        std::swap(temp._temp2, _temp2);
    }
    return *this;
}

That technique uses copy / swap. All that is being done is to create a temporary from the passed-in Stack object, and just swap out the current contents with the temporary's contents. Then the temporary dies off with the old contents.

Given all of this, the class should work correctly. Whether it is 100% correct with all of the other functions, that again is a different issue.

Edit:

Here is a fix for the copy constructor. Note we still use existing functions to make the copy:

  template <class T>
    Stack<T>::Stack(const Stack<T>& rhs) : _head(nullptr), _temp1(nullptr), _temp2(nullptr) {
        Node<T>* temp = rhs._head;
        Stack<T> tempStack;
        while (temp)
        {
            tempStack.push(temp->_data);
            temp = temp->_next;
        }
        while (!tempStack.empty())
        {
           push(tempStack.top()); 
           tempStack.pop();
        }
    }

This is not as efficient, but usually a stack data structure uses an underlying container such as vector where it is easy to reverse the underlying contents, and not based on a singly linked-list as you're using.

PaulMcKenzie
  • 31,493
  • 4
  • 19
  • 38
  • I'm assuming it is ok. But you see that in your destructor, you use `pop`, but you failed to use the same technique (function reuse) in the copy constructor by utilizing `push`. – PaulMcKenzie May 26 '20 at 12:39
  • I've _pushed_ 34, 67 & 92 and the copy-constructor shows 92, 67 & 34 whereas assignment shows as is i.e 34, 67, 92 in the output would there any problem with this? And also it says to make a deep copy; check edge cases & check whether you have guarded against self-assignment in the **Instructions** so would there be any major or minor tweaks to your code? – ash_k123 May 26 '20 at 16:20
  • All you need to do is fix the copy constructor -- maybe push onto a temporary stack, and pop back out, calling `push` for each item -- all of the code for that is already written, you just have to use it strategically. Don't adjust the assignment operator -- that will work out-of-the-box and does the check for self-assignment. As a matter of fact, the copy/swap method does not need a check for self-assignment, but I added one anyway. – PaulMcKenzie May 26 '20 at 16:34