What is a Stack?
Stack is an abstract data type that collects data. The basic operation contract for this project details: O(1) for push, pop, peek. The student will decide the backing data structure on his own. Possible options are: singly linked list, dynamic array.
What kind of Stack?
In this project, we’re making a Stack abstract data type that stores only integers.
Prerequisites
The student has to know how to write a class that will own a data structure that deals with allocation on the free store or heap. They should obviously know pointers and follow the rule of three. If they wish to implement the move assignment operator or the move constructor then that would be fulfill the move of 5 and make memory more efficient.
Instructions
Here’s a list of instructions to implement this project.
- Project Setup -
compile.sh,main.cpp,Stack.hpp,stack.cpp,replit.nix - Write constructor(s) with member initialization
- Rule of 3 - Write the needed declarations to satisfy the rule of three and implement them
- Testing - Setup Google Test and write some tests
- Implement the Rule of Three Declarations
- Implement
push(),pop(),peek(), andresize()
1. Project Setup
Replit
There isn’t any web platform that can host C++ that I know of besides Replit. The best tools are the tools that you own. Most of my students at TheCoderSchool, Irvine use a chromebook, so we opt to use Replit.
Skip If Needed
You can skip this section if you are not using a chromebook. The specific setup on Windows, MacOS, or Linux are not discussed. But this tutorial will work if you have the GNU compiler installed for C++. This means should have access to the program
g++on the path of your shell. This tutorial also only supports a POSIX shell likesh,bash, orzsh. You have to search for equivalent commands forpowershellon Windows. In terms of programs to install, you should install the GNU Compiler Collections and Google Test on your system.
New Replit projects uses nix for package management. So don’t reuse an old Replit application. The specifics is recorded in the Replit App Configuration or the Advanced Configuration page.
Creating an Empty Replit Project - 09/05/2026
If you click the
Newbutton, Replit will show you an AI command prompt. This is not what we want. Instead, find theImportbutton which should be underneath it. On theImportpage, select theEmptyoption.
We are going to install the GNU compiler for C++ which will allow us access to the program g++. Create replit.nix at the project’s root. Inside, paste this code in so that we install the GNU Compiler Collection and Google Test.
replit.nix{ pkgs } :
{
deps = with pkgs; [
libgcc
gtest
];
}What is Project Root?
A
project rootis the directory (synonymous with folder) where every file is stored. The directory that contains the project root will contain other files and directories external to your project.
Did editing replit.nix work?
To confirm whether the GNU compiler for C++ and GoogleTest is installed, open up Replit’s
shelland typeg++. If you receive an error that says command-not-found, you should retry copy-and-paste from what I have.
Creating Translation Units
Next, we are going to create the translation units needed for this project. In C++, these are simply your individual cpp files but also the header files hpp. Make the empty files stack.cpp, Stack.hpp, main.cpp.
Translation Unit
This term refers to the packaging of source code into a unit that is evaluated by the compiler. Translation units are different across programming languages. The translation unit of C and C++ is each file that is evaluated after the preprocessor has finished doing its job of copying content from include files. In another programming language like Rust, the translation unit we work with is modules, something you encountered in a managed language like Python or JavaScript.
Our First Executable
REPLIT uses Bash for its shell which is a shell derived from the POSIX standard. We’re going to create an sh file called compile.sh in the project root.
compile.shg++ main.cpp stack.cpp -o mainTo run the shell file, type this on the command line:
sh ./compile.shWhy compile.sh
Instead of typing in the long command every time we need to recompile. We just put the command in
compile.shas a shorter way to compile our project.
Right now, there’s going to be an error saying that there’s no entry to the program because there’s no main function.
As for tests, we’re going to create a separate sh file later.
Basic Source Code Setup
I suggest you type these out over direct copy-and-paste as it helps you remember the syntax.
main.cpp#include "Stack.hpp"
int main() {
return 0;
}Stack.hppclass Stack {
};stack.cpp#include "Stack.hpp"
// Nothing here yet
Now, run our compile.sh file again using the Replit shell.
sh ./compile.shYou should get a binary now in your project root called main, which is going to do nothing if you run it. Use the Replit shell and run it by just providing the path to the binary.
./mainWhat about header files?
Notice that our compilation command in
compile.shonly deals with thecppfiles. We don’t have to do additional setup because thehppfiles are in the same directory as thecppfiles. You should research more about what thepreprocessordoes. Because we “included” the header files, the content of the header files are copied inside thecppfile after thepreprocessorruns. Remember that thetranslation unitof C/C++ is onecppfile afterpreprocessing, the linker will automatically link the declarations with where the specific programming element is defined.
2. Writing a Constructor For Stack
This part is relatively simple. We declare the constructor Stack.hpp and define it in stack.cpp. You also need to think about what data members are needed for Stack ADT. Every ADT needs a backing data structure, in this project we’ll be using a dynamic array. Therefore, inside the class, we’ll need to have our first data member which is going to be a integer pointer holding the address of the first integer inside the contiguous heap-allocated array.
Dynamic Array and Related Terms
A
dynamic arrayis an array that change in size. What changing in size means is that the array will not be stored inside the object of class Stack. Instead, we have a data member which is a pointer which allows access to the heap allocated array. You can visualize this as having two containers of computer memory connected one-way by a pointer.The adjective
contiguousdescribes a collection whose elements’ memory addresses are next to each other. Contiguous memory is preferred by your CPU because of efficient cache operations → a concept calledcache localitywhich you can further research on.
We also have to consider any other necessary data members that’s useful to be holding on. When we heap allocate an array of an arbitrary type, there’s no way to get a hold of the size besides keeping the capacity of our heap allocated array. Besides our capacity, we should consider the logical length of the dynamic array.
Additional data members needed
Besides the
dynamic array, we also need to keep track of the currentcapacityand the currentlogical length.
Getting Length - An unsolvable problem for arrays and strings
Our memory slots, whose amount is measured in the unit bits, is by itself an array. You have to tell the computer when to stop reading the next slot of memory.
Strings- In C/C++, you will learn that the logical length for a string of 5 characters e.g."Hello"is actually 6. There’s an invisible null terminator that you have that’s automatically appended to any array of characters (string)s you make. The null terminator character\0tells us when the string ends.This is why we
keep lengthnotget length later.
Solution
You Must Try and Struggle - Don’t Look at Solution Yet
Don’t look at the solutions yet. Unless you’re confident that you got it right. Keep trying and confirm your implementation on your own. Your enemy is the lack of time management, especially in your academic courses.
3. Our Rule of Three
Rule of Three requires that for any class that has a pointer as its data member, the programmer has to implement the destructor, copy constructor, and copy assignment operator.
Destructor
Implement your destructor. How are you going to free your dynamic array when the Stack object is destructed?
Copy Constructor
When constructing from an existing Stack object, we have to allocate our dynamic array and copy integers over. Because this is a constructor, you should allocate your dynamic array with the member initialization list. You can copy integers by iterating over the Stack object we’re copying.
Solution
Copy Assignment Operator
The copy assignment operator decides what happens when you’re assigning an existing Stack object to a variable that already held another existing Stack object. Also remember that there’s no longer any member initialization because this is not a constructor.
Stack A{10};
Stack B{20};
A = B;You should also handle the case where self-assignment happens.
Stack A{10};
A = A; // Nothing should happen
Solution
Stack.hppclass Stack {
Stack& operator=(const Stack& other);
};stack.cppStack& Stack::operator=(const Stack& other) {
// self-assignment guard
if (this == &other) {
return *this;
}
delete[] data;
data = new int[other.size];
for (int i = 0; i < other.size; ++i) {
data[i] = other.data[i];
}
return *this;
}Trivia - Is
operator=an identifier?You may have heard people or some sources use the word
identifier. The answer to this trivia is No. An identifier is a syntactic form of a name. Simplified, you could think of it as type of name. Whenever you see a member function whose name does not conform to that of a standard identifier (think what name would be valid for a variable) then usually they are built-in and do something special. You can still use them normally e.g. inside a class:class A { A& operator=(const A& other) { // ... return *this; } void hey() { operator=(Stack a{}); // should be valid } };
WARNING - self-assignment guard implementation
if (*this == other) { return; }What’s wrong about this condition? What is the correct condition instead?
Hint: Are you comparing values for equivalence or memory addresses for equality?