Skip to Content
Course content

62: Practice Exercise: Building a Generic Stack Class

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen to almost every developer I've mentored. You decide to be "organized." You put your template class declaration in a .h file and the implementation in a .cpp file, just like you would with a regular class. Then, you hit compile and get a wall of "undefined reference" errors from the linker. It's incredibly frustrating because the code looks perfectly correct.

// Stack.h
template <typename T>
class Stack {
    T* data;
    int topIndex;
public:
    Stack();
    void push(T value);
    T pop();
};

// Stack.cpp
#include "Stack.h"
template <typename T>
void Stack<T>::push(T value) {
    // ... implementation logic ...
}

// main.cpp
#include "Stack.h"
int main() {
    Stack<int> myStack;
    myStack.push(10); // Linker Error: undefined reference to Stack<int>::push(int)
    return 0;
}

The Linker Error That Bites Everyone

Here is the deal: templates aren't actually code. They are blueprints for the compiler to generate code. When you write Stack<int>, the compiler says, "Okay, I need to create a version of this class where T is an int."

If the implementation is hidden away in Stack.cpp, the compiler doesn't see those definitions while it's compiling main.cpp. It assumes those functions will be provided by some other object file during the linking stage. But because Stack.cpp was compiled on its own, and the compiler didn't know which types (int, double, string) you'd actually need, it didn't generate any actual machine code for the methods. The linker looks for Stack<int>::push, finds nothing, and gives up.

Moving Implementation to the Header

The fix is simple, though it feels "wrong" if you're used to strict separation of concerns: you have to put the implementation in the header file. This ensures that whenever a .cpp file includes the header and requests a specific type, the compiler has the blueprint and the instructions right there to generate the code.

You can either put the logic directly inside the class definition or keep it below the class in the same file. I prefer putting it below the class; it keeps the interface clean and readable while still solving the linker problem.

// Stack.h
template <typename T>
class Stack {
    T* data;
    int topIndex;
public:
    Stack();
    void push(T value);
    T pop();
};

template <typename T>
void Stack<T>::push(T value) {
    // Now the compiler can see this when instantiating Stack<int>
    data[++topIndex] = value;
}

Designing the Interface for Flexibility

When you're building this for your exercise, don't just think about the "happy path." Think about the edge cases. What happens if someone calls pop() on an empty stack? If you just return a default-constructed T, the user has no way of knowing if the stack was empty or if the value they got just happened to be zero.

I recommend throwing a std::underflow_error or using an empty() method that the user is expected to check. Also, pay close attention to your const qualifiers. A method like peek() or top() shouldn't modify the stack, so it must be marked const. If you forget this, you won't be able to pass your stack into a function by const reference, which is how we pass almost everything in professional C++ to avoid expensive copies.




📋 Practical Task

Implement a Generic Stack for an RPN Calculator

Build a generic Stack<T> class from scratch that uses a dynamic array (or a std::vector if you prefer) as its internal storage. Once your class is implemented, use it to build a simple Reverse Polish Notation (RPN) evaluator.

  • The Stack: Implement push(T value), pop(), top(), and empty(). Ensure all implementation is in the header file to avoid linker errors.
  • The Evaluator: Create a function double evaluateRPN(std::string expression). This function should use your Stack<double> to process a string of numbers and operators (e.g., "3 4 + 2 *").
  • Edge Case Handling: Your stack should throw an exception if pop() or top() is called while the stack is empty.

Test your implementation with the expression "5 1 2 + 4 * + 3 -". If your stack and logic are correct, the result should be 14.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.