Non-Owning Views: Efficient Abstractions
C++17/20 introduced view types that refer to existing data without owning it, avoiding unnecessary copies.
std::string_view (C++17)
Lightweight non-owning reference to a string:
#include <string_view>
void print(std::string_view sv) { // No copy!
std::cout << sv << '\\n';
}
std::string str = "hello";
const char* cstr = "world";
print(str); // OK: string converts to string_view
print(cstr); // OK: const char* converts to string_view
print("literal"); // OK: no temporary std::string created
Performance Advantage
// Bad: Creates temporary string for each call
void process(const std::string& s);
process("literal"); // Allocates memory!
// Good: No allocation
void process(std::string_view s);
process("literal"); // Just pointer + length
string_view Operations
std::string_view sv = "Hello, World!";
sv.size(); // 13
sv.length(); // 13
sv.empty(); // false
sv.data(); // Pointer to first character
sv[0]; // 'H'
sv.front(); // 'H'
sv.back(); // '!'
sv.substr(7, 5); // "World"
sv.remove_prefix(7); // sv = "World!"
sv.remove_suffix(1); // sv = "World"
Lifetime Hazards
Critical: string_view doesn’t own data—ensure underlying data outlives the view:
std::string_view get_view() {
std::string temp = "temporary";
return temp; // DANGER: Returns view to destroyed string!
}
auto sv = get_view();
std::cout << sv; // UB: Dangling reference!
Safe usage:
std::string str = "persistent";
std::string_view sv = str; // OK: str outlives sv
void func(std::string_view sv) {
// OK: sv only used during function
}
std::span (C++20)
Non-owning view over contiguous sequence:
#include <span>
void process(std::span<int> data) {
for (int& x : data) {
x *= 2;
}
}
std::vector<int> vec = {1, 2, 3, 4, 5};
int arr[] = {1, 2, 3, 4, 5};
process(vec); // Works with vector
process(arr); // Works with array
Compile-Time vs Runtime Extent
// Dynamic extent (size at runtime)
std::span<int> dynamic_span = vec;
// Fixed extent (size at compile time)
std::span<int, 5> fixed_span = arr;
// fixed_span.size() is constexpr
static_assert(fixed_span.size() == 5);
span Operations
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> sp = vec;
sp.size(); // 5
sp.size_bytes(); // 5 * sizeof(int)
sp.empty(); // false
sp.data(); // Pointer to first element
sp[0] = 10; // Modify through span
sp.front() = 20;
sp.back() = 30;
// Subspans
auto first3 = sp.first(3); // First 3 elements
auto last2 = sp.last(2); // Last 2 elements
auto middle = sp.subspan(1, 3); // 3 elements starting at index 1
Read-Only Spans
void print(std::span<const int> data) { // const elements
for (int x : data) {
std::cout << x << ' ';
}
}
std::vector<int> vec = {1, 2, 3};
print(vec); // OK: converts to span<const int>
Use Cases
Replacing Multiple Overloads
Before:
void process(const std::vector<int>& v);
void process(const std::array<int, 10>& a);
void process(const int* data, size_t size);
After:
void process(std::span<const int> data); // Handles all cases
Safer C-Style Arrays
// Old: Easy to mismatch size
void process(const int* data, size_t size);
// New: Size is part of the span
void process(std::span<const int> data);
string_view vs const string&
| Aspect | string_view | const string& |
|---|---|---|
| Copy Cost | Cheap (2 pointers) | Depends (reference) |
| Temporary Creation | No | Yes (for literals) |
| Null-Termination | Not guaranteed | Guaranteed |
| Substring | O(1) | O(n) (copies) |
| Modify Original | No | No |
Guideline: Use string_view for read-only string parameters, especially when substrings are common.
span vs vector&
| Aspect | span | vector& |
|---|---|---|
| Type | View | Container |
| Ownership | No | Yes |
| Works With | Any contiguous | Only vector |
| Resize | No | Yes |
Guideline: Use span for functions operating on contiguous data regardless of container type.
Conceptual Check
Why is std::string_view potentially dangerous?
Runtime Environment
Interactive Lab
1#include <iostream>
2#include <string_view>
3#include <span>
4#include <vector>
5
6void print_sv(std::string_view sv) {
7 std::cout << "string_view: " << sv << " (size: " << sv.size() << ")\n";
8}
9
10void double_values(std::span<int> data) {
11 for (int& x : data) {
12 x *= 2;
13 }
14}
15
16int main() {
17 // string_view examples
18 print_sv("literal");
19 std::string str = "hello";
20 print_sv(str);
21
22 // span examples
23 std::vector<int> vec = {1, 2, 3, 4, 5};
24 double_values(vec);
25
26 std::cout << "After doubling: ";
27 for (int x : vec) std::cout << x << ' ';
28 std::cout << '\n';
29
30 return 0;
31}
System Console
Waiting for signal...