C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
62: Practice Exercise: Building a Generic Stack Class
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(), andempty(). 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 yourStack<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()ortop()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.
There are no comments for now.