So far, we’ve learned that a range of different options exist for representing strings. We’d ideally like to use just one type through our whole application, but that’s not always possible.
In complex projects, we’ll be using third-party libraries and operating system APIs that use different string types. We need to bring that all together to create a cohesive application, so we need a way to work with different string types in a somewhat organized way.
C++17 introduced string views, which are a useful tool that can help us achieve this.
The main problem string views intend to solve crops up when we’re dealing with string-based function arguments. Here, we are passing a C-style string into a function that accepts a std::string
:
#include <iostream>
void HandleString(const std::string& Input){
std::cout << Input;
}
int main(){
HandleString("Hello World");
}
As we’ve seen before, std::string
objects can be constructed from C-style strings, so our code works as expected:
Hello World
However, even though our parameter type is a reference, our function is being called with a different data type entirely. Creating a std::string
parameter from a char*
argument requires the argument to be copied, just as if we were passing it by value.
This is particularly problematic with strings as, under the hood, strings are just collections of characters. And like any other collection, creating a deep copy means every object in the collection needs to be copied. That’s bad for performance and gets worse the bigger the strings are.
A string view lets us circumvent the need to create a copy in scenarios like this. It provides a read-only interface for an underlying string.
String views work across many string types, providing a consistent set of methods to work with them without the performance hit of converting the strings to a single type.
The standard library’s implementation of a string view is std::string_view
, which is available by including <string_view>
#include <string_view>
std::string_view Input;
std::string_view
literals use double quotes with the sv
suffix. They are available after adding a using
statement for the std::string_view_literals
namespace:
#include <string_view>
using namespace std::string_view_literals;
std::string_view Input{"Hello"sv};
std::string_view
objects can be implicitly created from most built-in string types, including std::string
and C-style strings. They can also be created from other std::string_view
objects:
#include <iostream>
#include <string_view>
void HandleString(std::string_view Input){
std::cout << Input;
}
int main(){
using namespace std::literals;
HandleString("This was a C-style string");
HandleString("\nThis was a std::string"s);
HandleString("\nThis was a string_view"sv);
}
This was a C-style string
This was a std::string
This was a string_view
String views can also be constructed from a pair of iterators. This is most commonly used when we want our view to access only part of the underlying string:
#include <iostream>
#include <string_view>
void Log(std::string_view Input){
std::cout << Input << '\n';
}
int main(){
std::string Hello{"Hello World"};
Log({Hello.begin(), Hello.end()});
Log({Hello.begin(), Hello.begin() + 5});
Log({Hello.end() - 5, Hello.end()});
}
Hello World
Hello
World
Strings are collections of characters and, as we covered in our earlier lessons, the specific type of character is something we may want to change.
Just as std::string
is an alias for std::basic_string<char>
, std::string_view
is an alias for std::basic_string_view<char>
.
We can use different character types by changing the template parameter. Below, we create a string view that uses wchar_t
objects, which are 16-bit characters:
#include <iostream>
int main(){
using namespace std::string_literals;
std::basic_string_view<wchar_t> A{L"Hello"};
// std::basic_string_view<wchar_t>
// is aliased to std::wstring_view:
std::wstring_view B{L"World"};
std::wcout << A << ' ' << B;
}
Hello World
We covered character types, and their implications, in more detail earlier in this chapter:
A string view is little more than a lightweight pointer to a string managed by a different object entirely, Because of this, we must ensure that this underlying string object isn't deallocated when our view still needs it.
Similar to a dangling pointer, a view will become dangling when the string it was viewing no longer exists.
Trying to use a dangling view results in undefined behavior. Therefore, in long-running programs, we need to be mindful of our object lifecycles.
However, a common form of error involves creating a view that becomes dangling immediately, due to improper initialization. This happens when we initialize a string view with a value that is going to be cleaned up as soon as the expression ends.
The following code shows two examples where we create std::string
objects that will be deallocated immediately after our views are created, leaving both of them dangling:
#include <string_view>
std::string GetString(){
return {"Hello"};
}
int main(){
std::string_view A{GetString()};
using namespace std::string_literals;
std::string_view B{"World"s};
}
C-style string literals and string view literals have static duration, making them the recommended way to create string views of a static value:
#include <string_view>
int main(){
std::string_view A{"Hello"};
using namespace std::string_view_literals;
std::string_view B{"World"sv};
}
const
and constexpr
String ViewsWhilst the underlying string cannot be edited through a view, there are some ways the view itself can be changed. For example, we can reassign the view to point to a different string:
#include <string_view>
int main(){
std::string A {"Hello"};
std::string B {"World"};
std::string_view View{A};
View = B;
}
The next lesson will also introduce ways we can edit the view such that it has visibility of just part of the underlying string.
If we want to prevent these edits to a string view, we can mark it as const
in the usual way:
#include <string_view>
int main() {
std::string A {"Hello"};
std::string B {"World"};
const std::string_view View{A};
View = B;// Error - cannot reassign const
}
Additionally, string views can be marked as constexpr
. constexpr
string views do not have the same limitations as constexpr std::string
objects.
As a result, using a string_view
is the recommended way of having a constexpr
string in modern C++
constexpr std::string_view Name{"Bob"};
std::string
objects from String ViewsA std::string
can easily be created from a std::string_view
. However, this requires an expensive character-by-character copy, so the compiler will not allow it to be done implicitly:
#include <iostream>
#include <string_view>
void HandleString(const std::string& Input){
std::cout << Input;
}
int main(){
using namespace std::string_view_literals;
HandleString("Hello World"sv);
}
error: cannot convert argument 1 from 'std::string_view' to 'const std::string &'
It can be done explicitly, by directly calling the std::string
constructor, or using static_cast
:
#include <iostream>
#include <string_view>
void HandleString(const std::string& Input){
std::cout << Input;
}
int main(){
using namespace std::string_view_literals;
HandleString(std::string("Hello"sv));
HandleString(static_cast<std::string>(
" World"sv
));
}
Hello World
Creating a C-style string from a string view requires a little more thought. The data()
method on the string view returns a const char*
#include <string_view>
#include <iostream>
void HandleString(const char* Input){
std::cout << Input;
}
int main(){
using namespace std::string_view_literals;
std::string_view View{"Hello World"sv};
HandleString(View.data());
}
Hello World
However, we may not be able to use this as a C-style string because the string that the view was associated with may not have been null-terminated.
If the underlying string isn’t null-terminated (or we’re not sure), we should recreate the string to make sure.
Typically, this is done by generating a std::string
from the string view, and then using its c_str()
method:
#include <iostream>
#include <string_view>
void HandleString(const char* Input){
std::cout << Input;
}
int main(){
using namespace std::string_view_literals;
std::string_view View{"Hello World"sv};
HandleString(std::string(View).c_str());
}
Hello World
std::string_view
vs const std::string&
It seems there’s not much difference between a std::string_view
and a constant std::string
reference. Therefore, when we have a std::string
, and our function needs read-only access to it, which should we use?
When our argument is specifically a std::string
, there isn’t much difference. Both std::string_view
and const std::string&
will result in a read-only parameter, and both types can be initialized very efficiently from a std::string
, as no copying is needed.
However, we should still generally prefer to use a std::string_view
. Even if we know our function is only called with std::string
objects right now, things may change in the future.
And, as soon as someone calls our function with something like a char*
, the const std::string&
parameter type requires a copy, degrading performance. A std::string_view
parameter would have handled the different type with just the same efficiency.
Despite their desirable traits, there are a few scenarios where we shouldn’t be using string views as our function parameters.
The first scenario is when we need write access to the string. String views are read-only, so we can't use them for that scenario. Depending on our specific requirements, we need to pass the argument by value, or by a non-const
reference. Our parameter will then need to be a specific string type to copy into, or a specific reference type respectively.
The second scenario where we want to avoid string views is where our function is passing the argument off to another function, and where that function expects a specific string type.
Scenarios like this will typically involve us trying to recreate a concrete string type using our string view.
If we find ourselves doing that too often, we’re probably misusing string views and would be better served by just using traditional reference types instead.
In this lesson, we explored std::string_view
demonstrating its role in optimizing string handling by avoiding unnecessary copies while maintaining a consistent interface across different string types.
std::string_view
in C++17 as a tool for efficient string manipulation.std::string_view
provides a read-only view over strings, avoiding the performance cost of copying.std::string_view
from C-style strings, std::string
, and through literals.wchar_t
and the usage of std::wstring_view
.std::string
explicitly when necessary.std::string_view
and const std::string&
for function parameters to enhance performance.std::string_view
based on the need for write access or a specific string type.A practical introduction to string views, and why they should be the main way we pass strings to functions
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.