C++ / Basic / Lesson 5
Strings, vectors, and valid access
Use owning standard containers for text and sequences, and reason about indices, iteration, and invalidation before accessing their elements.
On this page
Lesson goals
- Manipulate std::string and std::vector using their public operations.
- Distinguish size, capacity, and valid element indices.
- Recognize UTF-8 byte counts and iterator invalidation.
A string owns a sequence of characters
std::string manages its own character storage and releases it automatically. Construction from a string literal copies the relevant characters into the string object. Concatenation creates meaningful text without manual buffer arithmetic. size reports the number of char elements, which are bytes on ordinary UTF-8 systems, rather than the number of visible letters. This course uses ASCII in output examples so counts are portable for those examples. Arabic text requires an encoding policy and a Unicode-aware library when grapheme boundaries, normalization, or display width matter.
Transform an owned string safely
The first example starts with one owned word, appends punctuation, and changes the initial element after confirming the string is nonempty. operator[] does not perform general bounds checking, so the validity of index zero follows from that condition. at provides checked access and throws std::out_of_range for an invalid index. Choose checked access at uncertain boundaries rather than expecting every implementation to diagnose an invalid subscript. A string can contain embedded null characters, so its size is the authority for its C++ sequence length.
#include <iostream>
#include <string>
int main() {
std::string word{"code"};
word += "!";
if (!word.empty()) { word[0] = 'C'; }
std::cout << word << " " << word.size() << "\n";
}
Code! 5
Appending adds one element. The guard justifies index zero, and five ASCII characters remain in the string.
Size creates elements; capacity reserves storage
std::vector<T> owns a contiguous sequence of T objects. size is the number of existing elements, while capacity describes storage available before growth requires reallocation. reserve changes capacity without creating elements; resize changes the number of elements. Therefore reserving ten slots does not make index nine valid in an empty vector. push_back appends an element and updates size. Prefer vector to a manually allocated dynamic array because it tracks construction, destruction, and storage together. Do not confuse the vector object lifetime with the lifetime of a borrowed pointer to its elements.
Iterate without unnecessary indexing
A range-based for loop expresses visiting each current element. Use a value variable for a small read-only copy, auto& for deliberate mutation, and const auto& for read-only borrowing of larger objects. The vector example reserves capacity, appends a fourth score, then sums the actual elements. The loop never inspects unused capacity. For index-based logic, the valid range is zero through size minus one, and empty sequences need careful handling. A comparison index < size avoids computing size minus one, which can wrap for an empty unsigned size.
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores{2, 4, 6};
scores.reserve(8);
scores.push_back(8);
int total{};
for (int score : scores) { total += score; }
std::cout << scores.size() << " " << total << "\n";
}
4 20
There are four elements totaling twenty. Capacity is intentionally not printed because its exact value is not the lesson contract.
Growth can invalidate borrowers
A vector reallocation invalidates all pointers, references, and iterators into its old element storage. Even without reallocation, appending invalidates the old end iterator, while erasure invalidates iterators and references at or after the erased position. Avoid appending to the same vector while traversing it with a range-for loop. Gather changes separately or use a carefully designed index algorithm. reserve can reduce reallocations but is not a lifetime proof for arbitrary future operations. Reacquire element access after structural modifications and document when returned views become invalid.
Common errors and fixes
Reserved vector elements cannot be accessed safely.
- Why it happens
- reserve allocated storage without increasing size.
- How to fix it
- Append elements or use resize before element access.
- Prevent it next time
- Check size, not capacity, when validating an index.
An element reference fails after push_back.
- Why it happens
- Growth reallocated the element storage.
- How to fix it
- Reacquire the reference after modification or change the design.
- Prevent it next time
- Track invalidating operations for every borrowed element.
An Arabic string size differs from visible letters.
- Why it happens
- UTF-8 stores many characters in multiple bytes.
- How to fix it
- Use Unicode-aware counting appropriate to the task.
- Prevent it next time
- Specify bytes, code points, or graphemes in requirements.
Check your understanding
1. Start with an empty vector, reserve twenty, and append 3 and 7. State which indices are valid and give its sum without assuming the exact capacity.
Show worked solution
After reserve the size is zero and no element index is valid. After two push_back calls the size is two, so only indices zero and one identify elements. The sum is ten. Capacity is at least twenty after a successful reserve, but the implementation may allocate more. Accessing index two with at throws rather than providing a third element.
2. An application saves a pointer to the first vector element, then appends many elements. Propose a safe way to remember which logical item was selected.
Show worked solution
Store the item index if the application only appends and does not reorder or erase earlier items. After growth, validate the index against the current size and obtain a fresh reference. If items can be reordered or removed, use a stable application identifier and look it up. A pointer into old storage does not become safe merely because the selected value still exists elsewhere.
Key takeaways
- Use owning containers and distinguish size from capacity.
- Validate bounds and record invalidation rules.
- A byte count is not a universal text length.