Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Creating Dynamic Arrays with std::vector
Create vectors with various constructors and initialize them with data.
Introduction to std::vector and List Constructors
In the previous lesson, we introduced containers and arrays. This lesson focuses on std::vector, the dynamic array type you'll use throughout your C++ programming.
Introduction to std::vector
std::vector is a container class in the C++ standard library that implements a dynamic array. Defined in the <vector> header as a class template, it takes a template type parameter defining the element type. Thus, std::vector<int> declares a vector whose elements are integers.
Creating a std::vector is straightforward:
#include <vector>
int main()
{
// Value initialization (uses default constructor)
std::vector<int> empty{}; // vector containing 0 int elements
return 0;
}
Variable empty is a std::vector with element type int. Using value initialization, our vector starts empty (with no elements).
An empty vector may not seem useful now, but we'll encounter this pattern frequently when working with dynamic data.
Initializing with a list of values
Since containers manage sets of related values, we typically initialize them with those values using list initialization:
#include <vector>
#include <string_view>
int main()
{
// List construction (uses list constructor)
std::vector<int> scores{ 85, 92, 78, 90 }; // 4 int elements
std::vector names{ "Alice"sv, "Bob"sv, "Carol"sv }; // 3 std::string_view elements using CTAD
return 0;
}
With scores, we explicitly specify std::vector<int>. The 4 initialization values create a vector with elements 85, 92, 78, and 90.
With names, we use C++17's CTAD (class template argument deduction) to deduce the element type from initializers. The 3 values create a vector with elements "Alice", "Bob", and "Carol".
List constructors and initializer lists
An initializer list is a braced list of comma-separated values (e.g., { 1, 2, 3 }).
Containers typically have a list constructor that constructs instances from initializer lists. The list constructor:
- Ensures the container has enough storage for all initialization values
- Sets the container's length to the number of elements in the initializer list
- Initializes elements to the values in sequential order
When you provide an initializer list, the list constructor is called.
Use list initialization with an initializer list of values to construct a container with those element values.
Accessing elements with the subscript operator
The most common way to access array elements is using the subscript operator (operator[]). Inside the brackets, provide an integral value (the subscript or index) identifying which element you want.
Arrays in C++ are zero-based: the first element has index 0, the second has index 1, and so on.
#include <iostream>
#include <vector>
int main()
{
std::vector values{ 10, 25, 50, 100, 200 }; // 5 int elements
std::cout << "First value: " << values[0] << '\n';
std::cout << "Second value: " << values[1] << '\n';
std::cout << "Sum of first three: " << values[0] + values[1] + values[2] << '\n';
return 0;
}
Output:
First value: 10
Second value: 25
Sum of first three: 85
The subscript operator returns a reference to the actual element, not a copy. You can use it just like a normal object.
Indexes are distances (offsets) from the first element. Starting at the first element and traveling 0 elements leaves you on the first element (index 0). Traveling 1 element reaches the second element (index 1).
Subscript out of bounds
The subscript must select a valid element. For an array of length N, valid indices are 0 through N-1.
operator[] performs no bounds checking—it doesn't verify the index is valid. Invalid indices cause undefined behavior.
In an array with N elements, the first element has index 0, the last has index N-1. There is no element with index N! Using N as a subscript causes undefined behavior.
Some compilers (like Visual Studio) provide debug-mode assertions that catch invalid indices. In release mode, these assertions are removed for performance.
Arrays are contiguous in memory
Array elements are always allocated contiguously—adjacent in memory with no gaps between them.
#include <iostream>
#include <vector>
int main()
{
std::vector values{ 10, 25, 50, 100, 200 };
std::cout << "An int is " << sizeof(int) << " bytes\n";
std::cout << &(values[0]) << '\n';
std::cout << &(values[1]) << '\n';
std::cout << &(values[2]) << '\n';
return 0;
}
Sample output:
An int is 4 bytes
0x1B4F720
0x1B4F724
0x1B4F728
The memory addresses are 4 bytes apart—the size of an int. This means arrays have no per-element overhead and allow random access (any element can be accessed directly rather than sequentially). This efficiency is a primary reason arrays are often preferred over other containers.
Constructing a vector of specific length
Sometimes you need a vector of a specific size before having values to fill it. You could use placeholder values:
std::vector<int> data{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // 10 zeros
But this is tedious and error-prone. Instead, use the explicit length constructor:
std::vector<int> data(10); // 10 int elements, value-initialized to 0
Each element is value-initialized (zero for int, default constructor for class types).
This constructor requires direct initialization (parentheses, not braces).
Non-empty initializer lists prefer list constructors
Consider this definition:
std::vector<int> data{ 10 }; // what does this create?
Two constructors could match:
{ 10 }as an initializer list, matched with the list constructor (creates a 1-element vector containing value 10){ 10 }as a single value, matched with the length constructor (creates a 10-element vector of zeros)
C++ has a special rule: when an initializer list is non-empty, a matching list constructor is preferred over other constructors. So { 10 } creates a 1-element vector containing 10.
When constructing a class type object using an initializer list: - Empty initializer list: default constructor is preferred over list constructor - Non-empty initializer list: matching list constructor is preferred over other constructors
Examples:
// Copy init
std::vector<int> v1 = 10; // Error: copy init won't match explicit constructor
// Direct init
std::vector<int> v2(10); // 10 elements, value-initialized to 0
// List init
std::vector<int> v3{ 10 }; // 1 element containing value 10
// Copy list init
std::vector<int> v4 = { 10 }; // 1 element containing value 10
// Default init
std::vector<int> v5{}; // Empty vector (0 elements)
std::vector<int> v6 = {}; // Empty vector (0 elements)
When constructing a container with arguments that are not element values (like a length), use direct initialization with parentheses.
Tip: When std::vector is a class member and needs a default size:
struct GameData
{
std::vector<int> scores{ std::vector<int>(5) }; // 5 elements, value-initialized
};
Direct initialization isn't allowed for member default initializers, so create a temporary vector with direct initialization and use it as the initializer.
Const and constexpr std::vector
std::vector can be const:
#include <vector>
int main()
{
const std::vector<int> values{ 10, 25, 50, 100, 200 }; // cannot be modified
return 0;
}
A const std::vector must be initialized and cannot be modified afterward. Its elements are treated as const.
The element type must not be const itself (e.g., std::vector<const int> is disallowed). A container's const-ness comes from const-ing the container, not the elements.
One significant limitation: std::vector cannot be constexpr. For constexpr arrays, use std::array instead.
Why is it called a vector?
In mathematics, "vector" typically means an object with magnitude and direction. So why is std::vector named this way?
Alexander Stepanov, one of the STL designers, wrote: "The name vector in STL was taken from the earlier programming languages Scheme and Common Lisp. Unfortunately, this was inconsistent with the much older meaning of the term in mathematics... this data structure should have been called array."
So std::vector is arguably misnamed, but it's too late to change.
Summary
std::vector basics: A dynamic array container defined in <vector> as a class template. Construct empty with std::vector<T>{} or with values using an initializer list.
List constructors: Containers have list constructors that construct instances from initializer lists, allocating storage, setting length, and initializing elements in order.
Subscript operator: Access elements with operator[] using zero-based indices. Returns a reference to the element. No bounds checking—invalid indices cause undefined behavior.
Contiguous memory: Array elements are stored adjacently in memory with no gaps, enabling efficient random access to any element.
Length constructor: Use direct initialization std::vector<T>(n) to create a vector with n value-initialized elements. Cannot use braces—that invokes the list constructor.
List constructor precedence: Non-empty initializer lists prefer list constructors over other constructors. { 10 } creates a 1-element vector, not a 10-element vector.
Const vectors: Can be const but not constexpr. Element type must not be const—const-ness comes from the container, not elements. Use std::array for constexpr arrays.
std::vector is your go-to container for dynamic arrays. Its flexibility, ease of use, and efficiency make it one of the most commonly used types in C++.
Creating Dynamic Arrays with std::vector - Quiz
Test your understanding of the lesson.
Practice Exercises
Introduction to std::vector
Practice creating and initializing std::vector containers using different constructors. Learn about list initialization and accessing elements.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!