C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
74: std::string_view for Efficient Views
I want to show you a piece of code that looks perfectly reasonable at first glance, but it's actually a ticking time bomb. This is a classic mistake I see developers make when they first start using std::string_view because they treat it like a "lightweight string" instead of what it actually is: a window into someone else's data.
#include <iostream>
#include <string>
#include <string_view>
std::string_view get_username_from_header(std::string_view header) {
// Let's say the header is "User: Alice" and we want just "Alice"
size_t pos = header.find(": ");
if (pos == std::string_view::npos) return "";
// We create a new string to "clean up" the result for the caller
std::string cleaned = std::string(header.substr(pos + 2));
// Oops! Returning a view of a local variable
return std::string_view(cleaned);
}
int main() {
std::string header = "User: Alice";
std::string_view user = get_username_from_header(header);
std::cout << "Welcome, " << user << "!" << std::endl; // Crash or garbage output
return 0;
}
The Danger of the Temporary String
If you run this, you might get "Welcome, Alice!", or you might get a crash, or you might get a string of random Chinese characters. It's a classic use-after-free bug. Here is what's happening: inside get_username_from_header, I created a std::string called cleaned. That variable lives on the stack. When the function returns, cleaned is destroyed, and its memory is freed.
A std::string_view doesn't own the characters it points to; it's just a pointer and a length. By returning a view of cleaned, you're handing the main function a pointer to memory that has already been reclaimed by the system. I've spent way too many hours debugging "heisenbugs" like this—where the code works on your machine but fails in production because the memory wasn't overwritten quite yet.
Switching to Non-Owning Slices
The beauty of std::string_view is that it allows us to avoid allocations entirely. The mistake in the code above was creating a temporary std::string. If we just operate on the view we were already given, we don't need to allocate anything, and the data remains valid as long as the original header string in main exists.
#include <iostream>
#include <string>
#include <string_view>
std::string_view get_username_from_header(std::string_view header) {
size_t pos = header.find(": ");
if (pos == std::string_view::npos) return "";
// Just return a slice of the original view.
// No new string is created, no memory is allocated.
return header.substr(pos + 2);
}
int main() {
std::string header = "User: Alice";
// The view now points directly into the 'header' string in main.
std::string_view user = get_username_from_header(header);
std::cout << "Welcome, " << user << "!" << std::endl;
return 0;
}
Now, the code is not only safe but significantly faster. We aren't calling the heap allocator to create a temporary string, and we aren't copying characters. We are simply sliding a window over the existing buffer.
When to Use View vs. Value
I generally follow a simple rule of thumb: use std::string_view for function arguments when the function only needs to read the string. It allows the function to accept both std::string and C-style string literals without forcing a conversion or an allocation.
However, be extremely cautious when returning a string_view. You must be absolutely certain that the data being viewed is owned by someone else who will outlive the view. If your function creates or modifies a string, return a std::string. If your function just finds a piece of an existing string, std::string_view is your best friend.
- std::string: Owns the memory. Use when you need to modify the text or ensure it stays alive.
- std::string_view: Borrows the memory. Use for read-only access to avoid expensive copies.
📋 Practical Task
Building a Zero-Allocation Log Parser
You are tasked with writing a high-performance log parser. You have a large log entry as a std::string, and you need to extract the Timestamp, Log Level, and Message without creating any new std::string objects (which would cause heap allocations).
Requirements:
- Create a
struct LogEntrythat contains threestd::string_viewmembers:timestamp,level, andmessage. - Write a function
LogEntry parse_log_line(std::string_view line). - The log format is:
[YYYY-MM-DD HH:MM:SS] [LEVEL] Message text here. - Use
find()andsubstr()to slice thestring_view. - In your
mainfunction, create astd::string` containing a sample log line, call your parser, and print the extracted fields to verify they are correct.
Hint: Remember that string_view::substr(pos, count) takes the starting position and the length of the slice, not the ending position.
There are no comments for now.