How to Write Our Own STL Container?
Last Updated :
20 Aug, 2024
The Standard Template Library (STL) in C++ provides us with various data structures and algorithms. We have STL containers like vector
, list
, map
, and set
that we commonly use to store and manipulate data efficiently. However, there are situations where the existing STL containers may not meet our specific requirements, so we may want to create our own custom container to optimize for particular use cases.
In this article, we will learn the concept of STL containers, why we might need to write our own, and how to create a custom STL-like container from scratch.
STL containers are data structures that store collections of objects and provide various functionalities, including adding, removing, and accessing elements, iterating through the collection, and more. The most commonly used STL containers are:
- Sequence Containers: Such as
vector
, deque
, and list
, which maintains the order of elements. - Associative Containers: Such as
set
, map
, multiset
, and multimap
, which store elements in sorted order, typically for fast retrieval. - Unordered Containers: Such as
unordered_set
, unordered_map
, unordered_multiset
, and unordered_multimap
, which stores elements in an unordered manner for constant-time average lookup.
These containers are designed with generic programming in mind and can be used with any data type. They are highly optimized and cover most general use cases.
Why Write Our Own STL Container?
While STL containers are highly efficient and versatile, there might be scenarios where a custom container is more beneficial:
- When the problem at hand requires a data structure that is not provided by the standard library, such as a specific tree structure or a graph, creating a custom container may be necessary.
- In performance-critical applications, a custom container tailored to the specific needs of the application can provide better performance than the general-purpose STL containers.
- Writing our own STL container is a great way to deepen our understanding of C++ and how the standard library works.
Steps to Write a Custom STL Container
To creating a custom STL container we need to follow several steps, like defining the container class, implementing iterators, and providing necessary member functions. Below, we will outline a basic approach to writing a custom container in C++.
- First define the container class template, specifying the type of elements it will store and any necessary member types, such as
value_type
, reference
, const_reference
, iterator
, and const_iterator
. - Implement the core functionality of the container includes managing memory allocation, adding and removing elements, and providing access to elements.
- Next, implement Iterators that allows us to traverse and manipulate the elements within the container. The iterators should support the necessary operations like incrementing, dereferencing, and comparing.
- Finally, test the custom container to ensure that the custom container behaves as expected. we can create a simple
main
function to test our container with different data types and operations.
C++ Program to Write Custom STL-Like Container
The below program demonstrate the implementation of custom STL-like container in C++.
C++
// C++ program to implement a custom container class template with basic operations.
#include <iostream>
#include <iterator>
#include <stdexcept>
using namespace std;
// Custom container class template
template <class T, class Allocator = allocator<T>> class MyContainer{
public:
// Type definitions for easier access to types used in the container
// Type of elements stored in the container
using value_type = T;
// Allocator type used for memory management
using allocator_type = Allocator;
// Reference type to the container's elements
using reference = value_type &;
// Constant reference type to the container's elements
using const_reference = const value_type &;
// Type for size and capacity of the container
using size_type = size_t;
// Type for difference between iterator positions
using difference_type = ptrdiff_t;
// Iterator class for the custom container
class iterator{
public:
// Type definitions for the iterator
// Category tag for random access iterators
using iterator_category = random_access_iterator_tag;
// Type of elements the iterator points to
using value_type = T;
// Type for difference between iterators
using difference_type = ptrdiff_t;
// Pointer type to the element
using pointer = T *;
// Reference type to the element
using reference = T &;
// Constructor to initialize the iterator with a pointer to an element
iterator(pointer ptr) : ptr_(ptr) {
}
// Dereference operator to access the element pointed to by the iterator
reference operator*() const{
return *ptr_;
}
// Arrow operator to access the element's members
pointer operator->() const{
return ptr_;
}
// Prefix increment operator to move the iterator to the next element
iterator &operator++(){
++ptr_;
return *this;
}
// Postfix increment operator to move the iterator to the next element and return the previous
// position
iterator operator++(int) {
iterator temp = *this;
++ptr_;
return temp;
}
// Equality operator to compare if two iterators are equal (point to the same element)
bool operator==(const iterator &other) const{
return ptr_ == other.ptr_;
}
// Inequality operator to compare if two iterators are not equal (point to different elements)
bool operator!=(const iterator &other) const{
return ptr_ != other.ptr_;
}
private:
pointer ptr_; // Pointer to the element the iterator currently points to
};
// Default constructor to create an empty container
MyContainer() : data_(nullptr), size_(0), capacity_(0){
}
// Copy constructor to create a container as a copy of another container
MyContainer(const MyContainer &other) : size_(other.size_), capacity_(other.capacity_){
// Allocate memory for the elements
data_ = allocator_type().allocate(capacity_);
// Copy elements from the other container
copy(other.data_, other.data_ + size_, data_);
}
// Destructor to clean up the allocated memory when the container is destroyed
~MyContainer(){
// Deallocate the allocated memory
allocator_type().deallocate(data_, capacity_);
}
// Member function to add a new element at the end of the container
void push_back(const T &value){
if (size_ == capacity_){
// If the container is full, allocate more memory
size_type new_capacity = capacity_ ? 2 * capacity_ : 1;
T *new_data = allocator_type().allocate(new_capacity);
copy(data_, data_ + size_, new_data);
allocator_type().deallocate(data_, capacity_);
data_ = new_data;
capacity_ = new_capacity;
}
data_[size_++] = value;
}
// Member function to remove the last element from the container
void pop_back(){
// Only remove if the container is not empty
if (size_ > 0){
--size_;
}
}
// Member function to access an element at a given index with bounds checking
reference at(size_type index){
if (index >= size_){
// Throw an exception if the index is invalid
throw out_of_range("Index out of range");
}
return data_[index];
}
// Const version of the 'at' function to access elements in a read-only context
const_reference at(size_type index) const{
if (index >= size_){
// Throw an exception if the index is invalid
throw out_of_range("Index out of range");
}
return data_[index];
}
// Member function to get the number of elements currently in the container
size_type size() const{
return size_;
}
// Member function to check if the container is empty
bool empty() const{
return size_ == 0;
}
// Member function to get an iterator pointing to the first element of the container
iterator begin(){
return iterator(data_);
}
// Member function to get an iterator pointing to the end (one past the last element) of the container
iterator end(){
return iterator(data_ + size_);
}
private:
// Pointer to the container's elements
T *data_;
// Number of elements in the container
size_type size_;
// Allocated capacity of the container
size_type capacity_;
};
// Main function to test the custom container
int main()
{
MyContainer<int> container;
container.push_back(1);
container.push_back(2);
container.push_back(3);
// Output the elements in the container after adding them
cout << "Container elements after push_back operations: ";
for (auto it = container.begin(); it != container.end(); ++it){
cout << *it << " ";
}
cout << endl;
// Remove the last element from the container
container.pop_back();
// Output the elements in the container after removing the last one
cout << "Container elements after pop_back operation: ";
for (auto it = container.begin(); it != container.end(); ++it){
cout << *it << " ";
}
cout << endl;
// Test accessing elements with bounds checking using the 'at' function
try{
cout << "Element at index 1: " << container.at(1) << endl;
cout << "Element at index 2: " << container.at(2) << endl;
}
catch (const out_of_range &e){
cerr << "Error: " << e.what() << endl;
}
return 0;
}
Output
Container elements after push_back operations: 1 2 3
Container elements after pop_back operation: 1 2
Element at index 1: 2
Element at index 2: Error: Index out of range
Similar Reads
Where to Use a Particular STL Container?
The Standard Template Library (STL) in C++ has a variety of containers designed to store collections of data. Each container has unique characteristics and is optimized for different operations. One common question that arises is which STL container to use in a specific scenario. In this article, we
3 min read
Can We Inherit STL Containers?
In C++, a common question that arises in our mind is: Can we inherit from Standard Template Library (STL) containers like std::vector, std::map, or std::list? The straightforward answer is no, we should not inherit from STL containers. In this article, we will learn why inheriting from STL container
6 min read
How to Manage Linux Containers using LXC
LXC is a technology for creating light weighted containers on your system. This technology is able to run on more than one Linux host. It is just like its counterparts i.e the other hypervisors like KVM and Xen. Unlike complete virtualization, containerization does not provide the user with complete
4 min read
Why does the C++ STL not provide any "tree" containers?
What is STL in C++? The Standard Template Library (STL) is a set of C++ template classes to provide common programming data structures and functions. It is a library of container classes, algorithms, functions and iterators. It is a generalized library and so, its components are parameterized. Worki
3 min read
How to create a List with Constructor in C++ STL
Lists are sequence containers that allow non-contiguous memory allocation. As compared to vector, list has slow traversal, but once a position has been found, insertion and deletion are quick. Normally, when we say a List, we talk about doubly linked list. For implementing a singly linked list, we u
2 min read
How to start or run docker daemon
Docker has had a significant impact on the development, shipping, and deployment of applications. By using containerization, Docker allows a developer to package an application along with all the dependencies into standard units termed containers. This way, it makes sure the application behaves unif
6 min read
Containers in C++ STL (Standard Template Library)
Standard Template Library (STL) provides the built-in implementation of commonly used data structures known as containers. A container is a holder object that stores a collection of other objects (its elements). They are implemented as class templates, which allows great flexibility in the data type
3 min read
How To Connect Docker to Local Network?
Docker, on the other hand, has changed paradigms around application development, deployment, and management in containerized environments. One of the strong features in Docker is robust networking support that allows different containers to communicate with each other and also with external networks
7 min read
Pointers in Containers in C++ STL
Prerequisites: Pointers in C++Container in STL There are different containers in C++ having their own features where if are adding extra functionality of pointer it will contribute in a different way. Although not all of the containers are useful in this context so let's see a few of the containers
5 min read
What's a Linux Container?
The Linux container includes one or more processes that are isolated from the rest of the system. All of the files required to run them are provided by a separate image, ensuring that Linux containers are portable and consistent as they go from development to testing and ultimately to production. Th
7 min read