This is the archaic form of template declaration:
template <class T>
The more modern form is:
template<typename T>
Technically not difference but class
implies user defined type while typename
implies any type. Most modern libraries will use the newer version. There is also one obscure corner case template templates were it makes a difference.
You don't need to define a constructor for node
.
struct node {
T data;
node* next = nullptr;
node(T data, node* next) : data(data), next(next) {};
};
The initializer list contruct will have the same affect.
struct node {
T data;
node* next;
};
...
new node {data, top};
Also I would use standard naming convention. User defined types start with an upper case letter while variables and methods start with a lower case letter. This helps you distinguish between object creation and function calls very easily.
You have missed the destructor. This means a non empty stack will leak all its members when it goes out of scope. So you should define a destructor to cleanup when the object is destoryed.
Now that you have a destructor:
You don't obey the rule of 3 or 5 (somebody else mentioned the rule of 7 but I don't know what that is, if somebody would like to clarify in the comments?).
Rule of 3: If you define a destructor, copy constructor or copy assignment then you probably need all three.
Rule of 5: Rule of 3 + the 2 move operators. Allows you define move operators for your object so that it is move compatible.
The alternative to using the Rule of 3/5 is to use the Rule of 0. This basically means the node
object cleans itself up. This would require using std::unique_ptr
. The top
in your list and the next
in the node would need to use this type.
Personally I don't think this is appropriate for building lists (but it is definitely an option and other people would recommend it).
You are passing by value here:
void Push(T value)
This is fine for simple types of T
. But if T
is expensive to copy then you copy it to the Push
method. Then you copy it again when you construct the object.
Try pushing by reference and moveable reference.
void Push(T const& value); // Pass value by reference.
void Push(T&& value); // Pass by r-value ref allowing a move.
This function does not change the state of the object.
bool IsEmpty() {
return top_ == nullptr;
}
You should probably mark it as const. This allows you to pass the stack by const reference
and still check the state of the object.
DRY you code.
The following code is repeated in two places
if (IsEmpty())
throw std::runtime_error("Stack is empty");
That means it is a candidate to be put into its own function.
You can not implement Pop()
that returns a value in an exception safe manner. As such the standard stack uses two separate methods top()
which returns a reference to the top value and pop()
that returns void
but removes the top item from the stack.
You should probably follow this convention.
These two functions return by value.
T Peek()
T Pop()
This means you make a copy of the value. This is fine for simple types like integer but for complex classes this is potentially expensive. Another reason to use top()
and void pop()
. But here at least Peek()
should return a reference to avoid the copy.
#include
lines, and amain()
that shows how to call your function. It can really help reviewers if they are able to compile and run your program. \$\endgroup\$std::forward_list
. If you really need a linked list, use that rather than rolling your own. \$\endgroup\$