HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Stack implementation with a linked list

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
stackwithimplementationlistlinked

Problem

Could anyone provide any constructive criticism? I'm trying to get a jump on data structures before classes start.

One thing is that I should add a copy constructor and assignment operator. Changes made: got rid of using namespace std.

As the code allows users to specify a cap, if they wish, for their stack any ideas of how I could signal that that particular stack is full? Currently I'm outputting a message from within the method but I don't like it when method handle output. What about a method that returns whether the stack is full?

```
#include

using namespace std; // TESTING

template
class Stack
{
public:
Stack()
: head(nullptr), capacity(0), currSize(0), sized(false)
{}
Stack(int cap)
: head(nullptr), capacity(cap), currSize(0), sized(true)
{}
~Stack();

void push(T val);
void pop();
bool isEmpty() const { return head == nullptr; }
int size() const;
T top() const;

private:
template
struct StackNode
{
StackNode()
:data(0), next(nullptr)
{}
StackNode(T val, StackNode *ptr = nullptr)
:data(val), next(ptr)
{}

T data;
StackNode *next;
};

StackNode *head;
int capacity;
int currSize;
const bool sized;
};

template
Stack::~Stack()
{
while (head != nullptr)
{
StackNode *tmp = head->next;
delete head;
head = tmp;
}
}

template
void Stack::push(T val)
{
if (sized == false || currSize != capacity)
{
head = new StackNode(val, head);
++currSize;
}
else cout
void Stack::pop()
{
if (head != nullptr)
{
StackNode *tmp = head;
head = head->next;
delete tmp;
--currSize;
}
}

template
int Stack::size() const
{
return currSize;
}

template
T Stack::top() const
{
if (head != nullptr)
{
return head->data;
}
else return NULL;
}

int main()
{
Stack myStack(3);

Solution

using namespace std; // TESTING


Judging by your comment, I'm guessing you're aware about the problems of exposing namespace members in the global scope. But did you know that you can using namespace inside scopes other than the global? It is fine if in your unit tests you want to expose the std stuff to make the code less verbose, but do that in the shortest possible scope then, in this case, inside main():

int main()
{
    // Make an exception since this is a unit test and expose
    // the whole Standard Library inside main(), so we don't have 
    // to std:: qualify everything.
    using namespace std;

    // same as before ...
}


cout << "Destroyed" << endl;  myStack.~Stack();


Very unusual for you to be calling the destructor for myStack. A destructor is called automatically when an object goes out of scope or is deleteed. There are very few cases where a programmer would manually call a destructor, those are not present here. If you just wan't to ensure the object is destroyed before the end of main(), to log some stuff, wrap its declaration inside a new scope. E.g.:

int main()
{
    {
        Stack myStack(3);
        ...
    } // <== ~myStack() called here
}


This way you won't risk ending with a half destroyed object in your hands.

template 
T Stack::top() const
{
    if (head != nullptr)
    {
        return head->data;
    }
    else return NULL;
    //          ^^^^ problem here!
}


That will not work for a T type that is not a pointer or integer. NULL can be assigned to integers because it is usually implemented as #define for 0. That's one of the reasons why you should use nullptr whenever possible (C++11). If you had used nullptr, your test with T=char would have failed and you would have noticed this problem.

The usual convention for a generic stack is to throw an exception if you try to access the top for an empty stack. Returning a "default" value is less generic and also makes it harder for the caller to detect errors. Returning a default also requires the type T to be default constructible, so don't do it.

Same goes for popping on an empty stack. Right now you are not generating any errors. You should consider throwing and exception. Deriving a StackUnderflow exception type from std::runtime_error might be a good idea. Then you can extend the concept to a StackOverflow error when trying to push to the bounded stack. Granted that you can do with a simple std::runtime_error, but defining custom exception classes is a nice exercise if that's your point for writing this implementation.

cout << "push: "; myStack.push('H'); cout << myStack.top() << endl;


Don't pack that many statements in a single line. People read shorter columns of text much faster. But not just that, mixing several statements together makes it a lot easier for a quick read to miss some important part of it. Put each statement in its own line.

cout << "push: "; 
myStack.push('H'); 
cout << myStack.top() << endl;


Not a huge thing, but usually this kind of data structures uses std::size_t for things like size and capacity. That's an unsigned integer, which makes sense since the stack size is not meant to be negative, however, there is some discussion about avoiding unsigned integers, so that's up to you to decide if it is worth it.

Last thing is a bit of a big subject, which was introduced with C++11, called move semantics. Your code does some unnecessary copying of data on push and StackNode's constructor, which might affect performance when T is something other that a native type or pointer. I'll leave you with a couple references you can read to further learn about this:

-
SO: What are move semantics?

-
cppreference.com: Move constructors.

Code Snippets

using namespace std; // TESTING
int main()
{
    // Make an exception since this is a unit test and expose
    // the whole Standard Library inside main(), so we don't have 
    // to std:: qualify everything.
    using namespace std;

    // same as before ...
}
cout << "Destroyed" << endl;  myStack.~Stack();
int main()
{
    {
        Stack<char> myStack(3);
        ...
    } // <== ~myStack() called here
}
template <typename T>
T Stack<T>::top() const
{
    if (head != nullptr)
    {
        return head->data;
    }
    else return NULL;
    //          ^^^^ problem here!
}

Context

StackExchange Code Review Q#97178, answer score: 7

Revisions (0)

No revisions yet.