All container classes are part of std name space. Most of the member functions of container classes are common and have common functionality. The decision of which container to use for specif need does not generally depend on the functionality of a container but the efficiency of some of it’s members. This is specifically true for sequence containers which provide different tradeoffs in complexities to insert/delete/access it’s elements. Learning STL will be effective only when you write programs using them. So I will not go behind every member of each container but give a brief overview of major container classes with simple examples. Complete details of each class you should consult the STL reference documents. Array class (std::array) Vector class (std::array) List class (std::list) Deque class (std::deque) Forward_list class (std::forward_list) Set class (std::set) Unordered_set class (std::unordered_set) Multiset class and unordered_multiset class (std::set & std::unordered_set) Maps: map, multimap, unordered_map and unordered_multimap (std::map, std::multimap, std::unordered_map and std::unordered_multimap) Stack class (std::stack) Queue class (std::queue) Priority_queue class (std::priority_queue) Array class (std::array) Header: <array> Declaration: template < class T, size_t N > class array; Arrays are fixed size sequence container. It takes the element type and number of elements in the array as template arguments. The size of the sequence is fixed at compile time. So no memory management and dynamic allocation involved. This class does not store any extra data other than the sequence of elements. It only adds layer of functions so that it works as a container. This is different than other containers whose size is dynamically changed. This has all the property of normal language defined array declared with bracket ([]) syntax. Zero size containers are valid but they should not be dereferenced. Example:std::array Code: #include<iostream> #include<array> using namespace std; int main() { //Creating an array container with braced initializer std::array<int,10> myarray{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //Accessing elements with [] operator for (int i = 0;i < 10;++i) myarray[i] = myarray[i] * 2; //Accessing elements using the pointer to the underlying array auto data = myarray.data(); for (int i = 0;i < 10;++i) *(data + i) += 1; // Accessing elements with at() function for (int i = 0;i < 10;++i) myarray.at(i) *=10; //Acdessing the first element cout << "The first element:" << myarray.front()<<endl; //accessing the last element cout << "The last element:" << myarray.back()<<endl; for (const int val : myarray) std::cout << val << " "; std::cout << std::endl; return 0; } Output: Code: The first element:1 The last element:10 1 2 3 4 5 6 7 8 9 10 Vector class (std::array) Header: <vector> Declaration: template < class T, class Alloc = allocator<T> > class vector; The instance of vector template class is a sequence container of a given data type. The elements are stored contiguously in memory. It stores the elements similar to a normal array but it can dynamically re-size the array by allocating and deallocating memory. To optimize the reallocation it allocates extra memory so that it does not need to reallocate memory every time an element is added. If you know the minimum number of elements it is better to allocate that by calling the reserve() method of the vector so that no reallocation is needed until the number of elements exceed the minimum number. Like array container, vector elements can be accessed directly using [] operator and offset on a regular pointer to it’s elements. Vector is very efficient in accessing its elements with array index. Adding elements at the end is efficient but inserting element in between is costly compared to other containers. This is because every time inserting an element require some elements to be moved. Also resizing has an overhead which is optimized by intelligent memory allocation. Example: std::vector Code: #include<iostream> #include<vector> using namespace std; int main() { //Creating a vector container with braced initializer std::vector<int> myvector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //Accessing elements with [] operator for (int i = 0;i < 10;++i) myvector[i] = myvector[i] * 2; //Accessing elements using the pointer to the underlying array auto data = myvector.data(); for (int i = 0;i < 10;++i) *(data + i) += 1; // Accessing elements with at() function for (int i = 0;i < 10;++i) myvector.at(i) *= 10; //Accessing the first element cout << "The first element:" << myvector.front() << endl; //Accessing the last element cout << "The last element:" << myvector.back() << endl; auto beginItr = myvector.begin(); auto endItr = myvector.end(); //Accessing elements using iterator while (beginItr != endItr) { std::cout << *beginItr << " "; ++beginItr; } std::cout << std::endl; return 0; } Output: Code: The first element:30 The last element:210 30 50 70 90 110 130 150 170 190 210 List class (std::list) Header: <list> Declaration: template < class T, class Alloc = allocator<T> > class list; List is a sequence container which stores the elements in a doubly linked list. The sequence of the elements is maintained by associating to each element of a link to the preceding element and another link to the following element. So each element size is increased by 2 pointers. But this has advantage of constant time to insert or remove elements when you have an iterator of the sequence. It also allows to iterate both direction with constant time. On the other hand you cannot access elements directly. You have to use iterator and move it to reach a desired position to access the element. Example of std::list Code: #include<iostream> #include<list> using namespace std; int main() { //Creating a list container with braced initializer std::list<int> mylist{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //Accessing elements with iterator auto beginItr = mylist.begin(); auto endItr = mylist.end(); while (beginItr != endItr) { *beginItr *= 2; ++beginItr; } //Acdessing the first element cout << "The first element:" << mylist.front()<<endl; //accessing the last element cout << "The last element:" << mylist.back()<<endl; for (const int val : mylist) std::cout << val << " "; std::cout << std::endl; return 0; } Output: Code: The first element:2 The last element:20 2 4 6 8 10 12 14 16 18 20 Deque class (std::deque) Header: <deque> Declaration: template < class T, class Alloc = allocator<T> > class deque; Template container class std::deque (double-ended-queue) is a sequence container and behaves similar to the std::vector class with the difference that deque can grow/shrink in both direction. You can add/remove elements to/from both the ends. Unlike vector it does not guarantee to store all the elements in contiguous memory location. That’s why accessing an element by using offset to a pointer to another element is not safe. This also makes the internal implementation of deque more complex than vector but it helps it to grow more efficiently in some scenarios, specially when having very long sequence and reallocation is expensive. Example of std::deque Code: #include<iostream> #include<deque> using namespace std; int main() { //Creating a deque container with braced initializer std::deque<int> mydeque{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //Adding elements at the end mydeque.emplace_back(11); //Adding elements at the beginning mydeque.emplace_front(0); //Accessing elements with [] operator for (int i = 0;i <mydeque.size();++i) mydeque[i] = mydeque[i] * 2; // Accessing elements with at() function for (int i = 0;i < mydeque.size();++i) mydeque.at(i) *= 10; //Accessing the first element cout << "The first element:" << mydeque.front() << endl; //Accessing the last element cout << "The last element:" << mydeque.back() << endl; //Getting reverse iterators auto rbeginItr = mydeque.rbegin(); auto rendItr = mydeque.rend(); //Accessing elements using reverse iterator cout << "Elements in reverse order: "; while (rbeginItr != rendItr) { std::cout << *rbeginItr << " "; ++rbeginItr; } std::cout << std::endl; return 0; } Output: Code: The first element:0 The last element:220 Elements in reverse order: 220 200 180 160 140 120 100 80 60 40 20 0 Forward_list class (std::forward_list) Header: <forward_list> Declaration: template < class T, class Alloc = allocator<T> > class forward_list; forward_list container class is a sequence container which stores the elements in a singly-linked list. Each element may be stored in different and unrelated memory locations. The sequence of the elements is maintained by associating to each element of a link to the following element. So each element size is lesser by 1 pointers than list. It is similar to list with the difference that unlike list it does not allow traverse backwards. Each element hold the reference to the next element but not the previous element. Each element having only single link, insertion and removal of element is more efficient than list. Forward list is more efficient than other containers like array, vector and deque in terms of insertion, deletion, moving and the algorithm like sort which use those operations. Unlike list, forward_list does not allow to access the elements directly. If you want to access to 7th elements you have to iterate from the beginning to the 7th element to access it. It is as efficient as a simple c-style singly-linked-list. Example of std::forward_list Code: #include<iostream> #include<forward_list> using namespace std; int main() { //Creating a forward_list container with braced initializer std::forward_list<int> flist{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; auto cBeforeBeginItr = flist.cbefore_begin(); cout << "Original list: "; while (++cBeforeBeginItr != flist.end()) { cout << *cBeforeBeginItr << " "; } std::forward_list<int> myflist = flist; //Adding elements at the beginning myflist.emplace_front(0); //Revering the elements myflist.reverse(); //Accessing the first element cout << "\n\nThe first element of final list:" << myflist.front() << endl; //Getting iterators auto cbeginItr = myflist.cbegin(); auto cendItr = myflist.cend(); //Accessing elements using iterator cout << "Final Elements: "; while (cbeginItr != cendItr) { std::cout << *cbeginItr << " "; ++cbeginItr; } std::cout << std::endl; return 0; } Output: Code: Original list: 1 2 3 4 5 6 7 8 9 10 The first element of final list:10 Final Elements: 10 9 8 7 6 5 4 3 2 1 0 Set class (std::set) Header: <set> Declaration: Code: template < class T, // set::key_type/value_type class Compare = less<T>, // set::key_compare/value_compare class Alloc = allocator<T> // set::allocator_type > class set; Set is an associative container and store the unique elements in a specific order. Internally the elements are stored in a binary search tree. The value of an element also identifies itself, means the value is itself the key. The elements cannot be modified once they are in the container but they can be removed or inserted to the container. Set containers are slower than unordered_set to access individual elements by their key but it allows direct iteration on their elements based on their order. It supports bi-directional iterator. Example of std::set Code: #include<iostream> #include<set> using namespace std; int main() { //Creating a set container with braced initializer with default order std::set<int> originalset{ 1, 2, 30, 4, 15, 6, 7, 8, 90, 10 }; auto myset = originalset; //Adding elements myset.emplace(0); myset.insert(40); myset.insert(13); auto endItr = originalset.end(); auto beginItr = originalset.begin(); cout << "Original list: "; while (beginItr != endItr) { cout << *beginItr << " "; beginItr++; } //Getting constant reverse iterators auto crbeginItr = myset.crbegin(); auto crendItr = myset.crend(); //Accessing elements using iterator cout << "\nFinal Elements in reverse order: "; while (crbeginItr !=crendItr ) { std::cout << *crbeginItr << " "; ++crbeginItr; } std::cout << std::endl; if (myset.find(9) != myset.end()) cout << "9 is present in the set" << endl; if (myset.find(10) != myset.end()) cout << "10 is present in the set" << endl; return 0; } Output: Code: Original list: 1 2 4 6 7 8 10 15 30 90 Final Elements in reverse order: 90 40 30 15 13 10 8 7 6 4 2 1 0 10 is present in the set Unordered_set class (std::unordered_set) Header: <unordered_set> Declaration: Code: template < class Key, // unordered_set::key_type/value_type class Hash = hash<Key>, // unordered_set::hasher class Pred = equal_to<Key>, // unordered_set::key_equal class Alloc = allocator<Key> // unordered_set::allocator_type > class unordered_set; unordered_set is an associative container and store the unique elements without any order. Internally the elements are stored in hash tables. The value of an element also identifies itself, means the value is itself the key. The elements cannot be modified once they are in the container but they can be removed or inserted in the container. unordered_set containers are faster than set to access individual elements by their key but slower in range iteration. It supports forward iterator. Example of std::unordered_set Code: #include<iostream> #include<unordered_set> using namespace std; int main() { //Creating an unordered_set container with braced initializer. Duplicate values are discarded std::unordered_set<int> originalset{ 1, 2,2, 30, 4, 15, 6, 7, 8, 90, 10 }; auto myset = originalset; //Adding elements myset.emplace(0); myset.insert(40); myset.insert(13); //Rempving element myset.erase(1); auto endItr = originalset.end(); auto beginItr = originalset.begin(); cout << "Original list: "; while (beginItr != endItr) { cout << *beginItr << " "; beginItr++; } cout << "\nFinal list: "; unordered_set<int>::iterator itr = myset.begin(); for (int i = 0;i < myset.size();++i) { cout << *itr << " "; ++itr; } std::cout << std::endl; if (myset.find(9) != myset.end()) cout << "9 is present in the set" << endl; if (myset.find(10) != myset.end()) cout << "10 is present in the set" << endl; return 0; } Output: Code: Original list: 1 90 2 6 30 4 7 15 8 10 Final list: 90 2 6 30 4 7 15 8 10 0 40 13 10 is present in the set Multiset class and unordered_multiset class (std::set & std::unordered_set) Header: <set> and <unordered_set> Declaration: Code: template < class T, // multiset::key_type/value_type class Compare = less<T>, // multiset::key_compare/value_compare class Alloc = allocator<T> > // multiset::allocator_type > class multiset; template < class Key, // unordered_multiset::key_type/value_type class Hash = hash<Key>, // unordered_multiset::hasher class Pred = equal_to<Key>, // unordered_multiset::key_equal class Alloc = allocator<Key> // unordered_multiset::allocator_type > class unordered_multiset; Multiset and unordered_multiset are similar to set and unordered_set respectively with the property that they can contain duplicate values. These classes are also declared in the same headers <set> and <unordered_set>. For unordered multi-set, duplicate valued elements are stored in the same bucket Example of std::multiset and unordered_multiset Code: #include<iostream> #include<set> #include<unordered_set> using namespace std; int main() { //Creating an multiset container with braced initializer with duplicate values std::multiset<int> myoset{ 1, 2,2, 30, 4, 15, 6,2, 7, 8, 90, 10 }; //Adding elements myoset.emplace(2); myoset.insert(15); myoset.insert(10); cout << "Ordered multiset:" << endl; for (int val : myoset) cout << val << " "; cout << endl << "The occurene of 2 in the set is " << myoset.count(2) << " times" << endl<<endl; //Creating an unordered_multiset container with braced initializer with duplicate values std::unordered_multiset<int> myuset{ 1, 2,2, 30, 4, 15, 6,2, 7, 8, 90, 10 }; //Adding elements myuset.emplace(2); myuset.insert(15); myuset.insert(10); cout << "Unordered multiset:" << endl; for (int val : myuset) cout << val << " "; cout << endl << "The occurene of 15 in the set is " << myuset.count(15) << " times" << endl; return 0; } Output: Code: Ordered multiset: 1 2 2 2 2 4 6 7 8 10 10 15 15 30 90 The occurene of 2 in the set is 4 times Unordered multiset: 1 2 2 2 2 6 30 4 7 15 15 8 90 10 10 The occurene of 15 in the set is 2 times Maps: map, multimap, unordered_map and unordered_multimap (std::map, std::multimap, std::unordered_map and std::unordered_multimap) Header: <map> and <unordered_map> Declaration: Code: template < class Key, // map::key_type class T, // map::mapped_type class Compare = less<Key>, // map::key_compare class Alloc = allocator<pair<const Key,T> > // map::allocator_type > class map; template < class Key, // multimap::key_type class T, // multimap::mapped_type class Compare = less<Key>, // multimap::key_compare class Alloc = allocator<pair<const Key,T> > // multimap::allocator_type > class multimap; template < class Key, // unordered_map::key_type class T, // unordered_map::mapped_type class Hash = hash<Key>, // unordered_map::hasher class Pred = equal_to<Key>, // unordered_map::key_equal class Alloc = allocator< pair<const Key,T> > // unordered_map::allocator_type > class unordered_map; template < class Key, // unordered_multimap::key_type class T, // unordered_multimap::mapped_type class Hash = hash<Key>, // unordered_multimap::hasher class Pred = equal_to<Key>, // unordered_multimap::key_equal class Alloc = allocator< pair<const Key,T> > // unordered_multimap::allocator_type > class unordered_multimap; Maps are like set but the difference between sets and maps is that every element in the map is a key-value pair and the map elements are arranged and accessed on the basis of key and not the value. Key and value can be off different types. In case of sets value itself is treated as the key. Both sets and maps are implemented same way- binary search tree for ordered map and hash tables for unordered map. Example of map, multimap, unordered_map and unordered_multimap Code: #include<iostream> #include<map> #include<unordered_map> using namespace std; int main() { //Creating different types of map container with braced initializer std::map<int, string> mymap{ {1,"Cat"},{6,"Lion"},{5,"Tiger"},{3,"Panther"},{4,"Leopard"} }; std::multimap<int, string> mymultimap{ { 1,"Cat" },{ 6,"Lion" },{ 5,"Tiger" },{ 3,"Panther" },{ 4,"Leopard" } }; std::unordered_map<int, string> myumap{ { 1,"Cat" },{ 6,"Lion" },{ 5,"Tiger" },{ 3,"Panther" },{ 4,"Leopard" } }; std::unordered_multimap<int, string> myumultimap{ { 1,"Cat" },{ 6,"Lion" },{ 5,"Tiger" },{ 3,"Panther" },{ 4,"Leopard" } }; //Inserting elements mymap.emplace(2,"Big Cat"); myumap.emplace(2, "Big Cat"); mymultimap.emplace(2, "Big Cat"); myumultimap.emplace(2, "Big Cat"); //Inserting duplicate elements mymap.emplace(2, "Big Cat"); myumap.emplace(2, "Big Cat"); mymultimap.emplace(2, "Big Cat"); myumultimap.emplace(2, "Big Cat"); cout << "Ordered map:" << endl; for (pair<int,string> item : mymap) cout <<"\""<< item.second.c_str()<<"\"" << ", "; cout << endl << "The occurrence of Big Cat in the set is " << mymap.count(2) << " times" << endl<<endl; cout << "Ordered multimap:" << endl; for (pair<int, string> item : mymultimap) cout << "\"" << item.second.c_str() << "\"" << ", "; cout << endl << "The occurrence of Big Cat in the set is " << mymultimap.count(2) << " times" << endl << endl; cout << "Unordered map:" << endl; for (pair<int, string> item : myumap) cout << "\"" << item.second.c_str() << "\"" << ", "; cout << endl << "The occurrence of Big Cat in the set is " << myumap.count(2) << " times" << endl << endl; cout << "Unordered multimap:" << endl; for (pair<int, string> item : myumultimap) cout << "\"" << item.second.c_str() << "\"" << ", "; cout << endl << "The occurrence of Big Cat in the set is " << myumultimap.count(2) << " times" << endl << endl; return 0; } Output: Code: Ordered map: "Cat", "Big Cat", "Panther", "Leopard", "Tiger", "Lion", The occurrence of Big Cat in the set is 1 times Ordered multimap: "Cat", "Big Cat", "Big Cat", "Panther", "Leopard", "Tiger", "Lion", The occurrence of Big Cat in the set is 2 times Unordered map: "Cat", "Lion", "Tiger", "Panther", "Leopard", "Big Cat", The occurrence of Big Cat in the set is 1 times Unordered multimap: "Cat", "Lion", "Tiger", "Panther", "Leopard", "Big Cat", "Big Cat", The occurrence of Big Cat in the set is 2 times Stack class (std::stack) Header: <stack> Declaration: template <class T, class Container = deque<T> > class stack; Container adapter class stack is designed for the LIFO(last-in first-out) context where elements are inserted and removed only at and from one end. The element last inserted will be removed first. Inserting an element is called push operation and removing an element is called pop operation. This is a container adapter and use some other container to store the elements. The underlying container types should support following operations: empty size back push_back pop_back vector, deque and list classes fulfills those requirement. Default container is deque. You can specify the container type while instantiation. Elements are inserted and removed from the back of the underlying container. Example of std::stack Code: #include<iostream> #include<stack> #include<vector> using namespace std; int main() { vector<int> v{1,2,3,4,5,6,7,8,9,10}; //Stack initialize with vector object stack<int, vector<int>> mystack(v); cout << "Size of the initialized stack: " << mystack.size()<<endl; while (!mystack.empty()) { cout << mystack.top() << " "; mystack.pop(); } cout << endl; for (int i = 1;i < 6;++i) { mystack.emplace(i*i); //emplace() is the better way than push() to insert an element } cout << "Size of the new stack: " << mystack.size() << endl; while (!mystack.empty()) { cout << mystack.top() << " "; mystack.pop(); } cout << endl; return 0; } Output: Code: Size of the initialized stack: 10 10 9 8 7 6 5 4 3 2 1 Size of the new stack: 5 25 16 9 4 1 Queue class (std::queue) Header: <queue> Declaration: template <class T, class Container = deque<T> > class queue; Container adapter class queue is designed for the FIFO(first-in first-out) context where elements are inserted at one end and removed from the other end. The element first inserted will be removed first and inserted last will be remove last. Inserting an element is called enque operation and removing an element is called dequeue operation. This is a container adapter and use some other container to store the elements. The underlying container types should support following operations: empty size back front push_back pop_front deque and list classes fulfills those requirement. Default container is deque. You can specify the container type while instantiation. Elements are inserted at the back of the container and remove from the front. Example of std::queue Code: #include<iostream> #include<queue> #include<list> using namespace std; int main() { list<int> lst{1,2,3,4,5,6,7,8,9,10}; //Queue initialize with list object queue<int, list<int>> myqueue(lst); cout << "Size of the initialized queue: " << myqueue.size()<<endl; while (!myqueue.empty()) { cout << myqueue.front() << " "; myqueue.pop(); } cout << endl; for (int i = 1;i < 6;++i) { myqueue.emplace(i*i); //emplace() is the better way than push() to insert an element } cout << "Size of the new queue: " << myqueue.size() << endl; while (!myqueue.empty()) { cout << myqueue.front() << " "; myqueue.pop(); } cout << endl; return 0; } Output: Code: Size of the initialized queue: 10 1 2 3 4 5 6 7 8 9 10 Size of the new queue: 5 1 4 9 16 25 Priority_queue class (std::priority_queue) Header: <queue> Declaration: Code: template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> > class priority_queue; Container adapter class priority_queue is designed such a way that it’s greatest element is always the first element according to a strict weak ordering criterion. Internally it maintains a heap structure where an element can be inserted at any moment and only the max heap element can be retrieved which is at the top of the priority_queue. This is a container adapter and use some other container to store the elements. The underlying container types should support following operations: empty size front push_back pop_back deque and vector classes fulfills those requirement. Default container is vector. You can specify the container type while instantiation. Elements are popped from the back of the specific container. Support of random access iterator is required to keep a heap structure internally all the time. This is done by the container adapter by automatically calling algorithm functions make_heap, push_heap and pop_heap when needed. Example of std::priority_queue Code: #include<iostream> #include<queue> #include<functional> using namespace std; int main() { deque<int> dq{1,10,3,4,6,5,7,8,9,2}; //Priority que will keep the smallest number at the top priority_queue<int, deque<int>, greater<int>> mypqueue(greater<int>(), dq); cout << "Size of the initialized priority queue: " << mypqueue.size()<<endl; while (!mypqueue.empty()) { cout << mypqueue.top() << " "; mypqueue.pop(); } cout << endl; for (int i = 1;i < 6;++i) { mypqueue.emplace(i*10); //emplace() is the better way than push() to insert an element } cout << "Size of the new priority queue: " << mypqueue.size() << endl; while (!mypqueue.empty()) { cout << mypqueue.top() << " "; mypqueue.pop(); } cout << endl; return 0; } Output: Code: Size of the initialized priority queue: 10 1 2 3 4 5 6 7 8 9 10 Size of the new priority queue: 5 10 20 30 40 50 Common Functions STL containers are not only generic in terms of type of elements but also in terms of functionality. There are lot of functionality and members are common across the containers. Here are major examples: All sequence containers support uniform initialization and assignment operator=(). Containers like vector, array, deque, and map support element access operator [] and function at() which take the index or a key value and return the element. All the containers support begin(), end(), cbegin() and cend() member functions which return random access(for array, vector, deque), bidirectional(for list and ordered associative containers) or forward iterators (for forward_list and unordered associative containers) or the constant version of those iterators Containers except forward_list and unordered associative containers support rbegin(), rend(), crbegin() and crend() which return random access (for array, vector, deque) and bidirectional (list and ordered associative containers) reverse iterators or constant versions of those iterators. All the containers except the forward_list support size() member function which returns the number of elements in a container. All containers support max_size() member function which return the number of maximum elements a container can hold. All sequence containers except array support resize() member function which re-size the container so that it can hold a given number of elements. All the containers support member function empty() which returns true if the container is empty, else return false. All containers except array and forward_list, support emplace() (or emplace_after() ), insert( or insert_after() ) and erase( or erase_after() ) member functions which insert a new element or remove an existing element at the given position specified by the given iterator. All containers except array support the clear() member function which remove all the elements and make the size of the container to 0. All containers support swap() member function which replaces its elements with the elements of another given container of same type. All sequence containers except array and forward_list support emplace_back(), push_back(), and pop_back() functions to insert a new element at the end of the container or remove the first element. Sequence container except array and vectors support emplace_front(), push_front() and pop_front() functions to insert a new element at the front or remove the first element.