std::string
Objectsstd::string
objects and their memoryIn this lesson, we’ll finish our exploration of the std::string
type by investigating:
std::string
methods and operators for manipulating the string’s contentsstd::string
objects, drastically increasing our optionsstd::basic_string
class, which allows us to customize the character type our strings useWe can append content to the end of an existing string, using the append()
method, or the +=
 operator:
#include <iostream>
#include <string>
int main(){
std::string Greeting{"Hello"};
Greeting.append(" World");
Greeting += '!';
std::cout << Greeting;
}
Hello World!
The append()
method also has some alternative signatures, giving us more options.
For example, we can append a sequence of characters:
#include <iostream>
#include <string>
int main(){
std::string Greeting{"Hello"};
Greeting.append(10, '!');
std::cout << Greeting;
}
Hello!!!!!!!!!!
Or we can append a substring by passing the starting position in the string to append, and the number of characters to append:
#include <iostream>
#include <string>
int main(){
std::string Greeting{"Hello "};
std::string Options{"World,Everyone,Friends"};
// Starting from position 15, append 7 characters
Greeting.append(Options, 15, 7);
std::cout << Greeting;
}
Hello Friends
append_range()
We can also append a range to a string using the append_range()
method. This will typically be the case when what we want to append is in a different type of container, or the result of an algorithm:
#include <iostream>
#include <string>
#include <array>
int main(){
std::string Greeting{"Hello "};
std::array Characters{
'W', 'o', 'r', 'l', 'd'};
Greeting.append_range(Characters);
std::cout << Greeting;
}
Hello World
Note: append_range()
was added to the specification in C++23, so may not yet be available on all compilers.
If we need to insert content into the middle of a string, we have two main options: insert()
and insert_range()
The most common use of insert()
involves passing the insertion position, and the string to insert:
#include <iostream>
#include <string>
int main(){
std::string Greeting{"Hello!"};
Greeting.insert(5, " World");
std::cout << Greeting;
}
Hello World!
insert_range()
Since C++23, we also have the insert_range()
method, which allows us to insert content from any range-based container. Unlike the basic insert()
, the insert_range()
requires an iterator to specify the insertion position.
std::string
objects come with the usual suite of iterators which we can generate from methods like begin()
:
#include <iostream>
#include <string>
#include <array>
int main(){
std::string Greeting{"Hello!"};
std::array Characters{
' ', 'W', 'o', 'r', 'l', 'd'};
Greeting.insert_range(Greeting.begin() + 5,
Characters);
std::cout << Greeting;
}
Hello World!
Note: insert_range()
was added to the specification in C++23, so may not yet be available on all compilers.
We cover iterators in more detail in our earlier chapter, covering containers:
The erase()
method gives us the ability to remove all or some of the characters in our string.
In its most basic usage, the erase()
method accepts a starting position, and the quantity of characters to erase.
#include <iostream>
#include <string>
int main(){
std::string Greeting{"Hello World!"};
// Starting from index 5, erase 6 characters
Greeting.erase(5, 6);
std::cout << Greeting;
}
Hello!
Both arguments to erase()
are optional. By default, the first argument is 0
, ie the beginning of the string, and the second argument is the end of the string:
// Erase all characters
MyString.erase();
// Erase everything except the first 5 characters
MyString.erase(5);
// Erase the first 6 characters
MyString.erase({}, 6);
Finally, we can replace parts of our string, effectively combining both an erase()
and an insert()
.
The most common usage of the replace()
function involves replacing part of the calling string with new content. This involves passing three arguments:
The number of characters to replace does not need to be the same as the number of characters we’re replacing it with. The string will resize as needed:
#include <iostream>
#include <string>
int main(){
std::string Greeting{"Hello World!"};
// Starting from index 6, replace 5 characters
Greeting.replace(6, 5, "Everyone");
std::cout << Greeting;
}
Hello Everyone!
replace_with_range()
Since C++23, we also have the replace_with_range()
function. It behaves similarly but allows the replacement content to be sourced from a range. Instead of a starting index and a count, we provide an iterator pair representing the range of characters we want to replace in the original string:
#include <iostream>
#include <string>
#include <array>
int main(){
std::string Greeting{"Hello World!"};
std::array Characters{
'E', 'v', 'e', 'r', 'y', 'o', 'n', 'e'};
Greeting.replace_with_range(
Greeting.begin() + 6, Greeting.begin() + 11,
Characters);
std::cout << Greeting;
}
Hello Everyone!
Note: replace_with_range()
was added to the specification in C++23, so may not yet be available on all compilers.
Like many standard library collections, std::string
includes the usual suite of iterators. This allows traversal through its characters in a standardized way.
The most basic implication of this is that we use a range-based for loop to iterate over the characters of a std::string
:
#include <iostream>
#include <string>
int main(){
std::string Greeting{"Hey"};
for (const char& Character : Greeting) {
std::cout << Character << '\n';
}
}
H
e
y
These iterators also allow std::string
s to be compatible with iterator-based algorithms.
In this example, we use the std::reverse()
algorithm to reverse the characters in a std::string
:
#include <iostream>
#include <string>
#include <algorithm>
int main(){
std::string String{"12345"};
std::reverse(String.begin(), String.end());
std::cout << String;
}
54321
Since std::string
objects are also ranges, we can use all the range-based features we covered in the earlier algorithms chapter, such as views, projection, and range-based algorithms.
Here, we approximate the number of words in a string by using the std::ranges::count()
algorithm to return the number of spaces it has:
#include <algorithm>
#include <iostream>
#include <string>
int main(){
std::string String{"The quick brown fox"};
std::cout << "The string has "
<< std::ranges::count(String, ' ') + 1
<< " words";
}
The string has 4 words
Each character in a std::string
has the basic char
type, but we can change this.
In reality, std::string
is nothing more than an alias for another type: std::basic_string<char>
.
That means it is simply defining the template type of a std::basic_string
, but we can do that ourselves:
#include <iostream>
#include <string>
int main(){
std::basic_string<char> String{"Hello\n"};
std::cout << String;
if constexpr (std::same_as<
decltype(String), std::string>) {
std::cout << "That was a std::string";
}
}
Hello
That was a std::string
As such, we can also use any other character type, not just a basic char
. Here, we create a string of "wide" characters, using the wchar_t
 type.
Before we do this, there are two things to note. First, we cannot initialize "wide" character strings from regular character strings, like a std::string
, a char*
, or a char[]
.
Instead, we need to use wide character strings, such as a wchar_t*
or wchar_t[]
. The language provides a literal for creating these: L""
which we’re using below:
std::basic_string<wchar_t> String{L"Hello"};
Secondly, we also can’t send wide characters to a stream intended for narrow characters. We need to send them to a stream that accepts wide characters. std::wcout
is the wide variation of std::cout
#include <iostream>
#include <string>
int main(){
std::basic_string<wchar_t> String{L"Hello"};
std::wcout << String;
}
Hello
We covered "wide" characters in our earlier lesson on Characters:
As an aside, similar to std::string
being an alias, most of the std::basic_string
variations using other built-in characters have also been aliased to shorter names.
For example, std::basic_string<wchar_t>
is available through the alias std::wstring
At this point, the similarities between a std::string
and a dynamic array are hopefully clear. The std::string
container manages memory in much the same way as a resizable array, such as std::vector
.
It occupies a block of memory in which to store its characters.
If that memory is too small to accommodate an action we take, such as appending a load of additional characters, the std::string
class takes care of that for us behind the scenes.
It finds a new, larger block of memory to occupy, and it copies all the characters to this new location and then releases its previous memory.
In most cases, we can just ignore that mechanism, but it’s worth spending a moment to understand what’s going on, especially as we can get some performance benefits from intervening.
Similar to std::vector
, the std::basic_string
types have three methods to investigate the current state of the string’s memory situation:
size()
returns the number of characters that are currently in the string. This is synonymous with the length()
method.capacity()
returns how many characters can be stored before the string needs to reallocate more memory and move to a bigger homemax_size()
is the maximum size the string can grow to, in the current environment. Most actions that would take the string’s size beyond this will throw a std::length_error
exception#include <iostream>
#include <string>
int main(){
std::string String{"Hello"};
std::cout << "Size: " << String.size();
std::cout << "\nCapacity: "
<< String.capacity();
std::cout << "\nMax Size: "
<< String.max_size();
}
Size: 5
Capacity: 15
Max Size: 9223372036854775807
Occasionally, we may want to intervene in this process. There are three main methods for doing this
reserve()
The resizing process can be a performance drain. Reallocating memory and moving the characters to the new location takes time.
If we know a string is going to grow through the duration of our program, it can be helpful to just reserve enough space right from the start, reducing the number of reallocations needed.
We can do this by calling the reserve()
method, passing a number representing how many characters we want to reserve space for. Our string will be moved to a location that can accommodate that capacity or slightly more:
#include <iostream>
#include <string>
int main(){
std::string String{"Hello"};
std::cout << "Capacity: "
<< String.capacity();
String.reserve(1'000);
std::cout << "\nNew Capacity: "
<< String.capacity();
}
Capacity: 15
New Capacity: 1007
As of C++20, if the argument we pass to reserve()
is lower than the current capacity, the function call has no effect.
With older compilers, it may reduce the capacity()
of the string, but not below the size()
. That is, it will only reduce excess capacity - existing characters will not be removed.
resize()
The resize()
method changes the current size of the string. If the argument we pass to resize()
is smaller than the current size, excess characters are pruned:
#include <iostream>
#include <string>
int main(){
std::string String{"Hello World"};
String.resize(5);
std::cout << String;
}
Hello
If the argument is larger than the current size, we fill the surplus with additional characters. These will be null characters (\0
) by default. However, we can pass a second argument to resize()
to specify what we want the surplus to be filled with:
#include <iostream>
#include <string>
int main(){
std::string String{"Hello"};
String.resize(10, '!');
std::cout << String;
}
Hello!!!!!
shrink_to_fit()
The shrink_to_fit()
function requests that the capacity of the string be reduced to match the size()
, or marginally more. This is useful if we no longer expect our string to grow, and we want to release the surplus memory:
#include <iostream>
#include <string>
int main(){
std::string String;
String.reserve(1'000);
for (int i = 0; i < 100; ++i) {
String += "Hello";
}
std::cout << "Size: " << String.
size();
std::cout << "\nCapacity: " << String.
capacity();
String.shrink_to_fit();
std::cout << "\n\nSize: " << String.
size();
std::cout << "\nCapacity: " << String.
capacity();
}
Size: 500
Capacity: 1007
Size: 500
Capacity: 511
In this lesson, we've explored more of the capabilities of std::string
, covering everything from basic modifications to advanced memory management techniques.
std::string
using append()
and the +=
operator.insert()
and insert_range()
for adding content at specific positions within a string.erase()
and its different overloads.replace()
and understanding the replace_with_range()
function.std::string
iterators and standard library algorithms to manipulate and analyze string content.std::string
to wide characters using std::basic_string<wchar_t>
.std::string
memory with methods like reserve()
, resize()
, and shrink_to_fit()
to optimize performance.std::string
ObjectsA practical guide covering the most useful methods and operators for working with std::string
objects and their memory
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.