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
73: std::string in Depth
You've been using std::string since the start of this course, and for the most part, it just works. You push characters in, you print them out, and the memory management happens behind the scenes. But as we move into more performance-critical code, "it just works" isn't enough. We need to know exactly what's happening under the hood, because std::string is doing a lot more than it lets on.
Let's look at a real scenario. Imagine we're building a system to parse usernames from a large configuration file. We need to trim whitespace and check if the username starts with a specific prefix like "admin_".
The Hidden Cost of Slicing
My first instinct might be to write a helper function that returns a "cleaned" version of the string. Let's try this:
std::string trim_prefix(std::string s) {
if (s.substr(0, 6) == "admin_") {
return s.substr(6);
}
return s;
}
// Usage
std::string user = "admin_jdoe_99";
std::string clean_name = trim_prefix(user);
It looks clean, right? But if we're doing this for ten thousand users in a loop, we have a problem. Every time I call s.substr(), C++ allocates a brand new block of memory on the heap, copies the characters into it, and returns a new std::string object. Then, when that temporary string goes out of scope, it's deleted. We're hammering the allocator for no good reason. I'm not changing the characters; I'm just changing where I'm looking at them.
Stopping the Bleeding with string_view
Since C++17, we have a tool for this: std::string_view. Think of it as a "window" into an existing string. It doesn't own the memory; it just holds a pointer to the start and a length.
Let's rewrite that function and see how the signature changes:
#include <string_view>
std::string_view trim_prefix_fast(std::string_view s) {
if (s.substr(0, 6) == "admin_") {
return s.substr(6);
}
return s;
}
// Usage
std::string user = "admin_jdoe_99";
std::string_view clean_name = trim_prefix_fast(user);
Now, s.substr() on a string_view doesn't allocate anything. It just returns a new string_view` that points slightly further into the original user string. It's incredibly fast. But here's the catch—and I've tripped over this more times than I'd like to admit: Lifetime. If the original std::string user is destroyed, clean_name becomes a dangling pointer. You're looking through a window at a house that's been demolished. Use string_view for read-only access and temporary processing, but convert it back to a std::string if you need to store the result long-term.
Where is the Data Actually Living?
I've always wondered why small strings seem to perform almost as well as stack-allocated arrays. I decided to test this by printing the memory address of the internal buffer for strings of different lengths.
std::string short_str = "Hi";
std::string long_str = "This is a much longer string to trigger allocation";
printf("Short: %p\n", (void*)short_str.data());
printf("Long: %p\n", (void*)long_str.data());
When I ran this, I noticed something strange. The address for short_str was very close to the address of the short_str object itself. The address for long_str, however, was far away in the heap. This is Small String Optimization (SSO). Most modern C++ libraries have a small internal buffer (usually 15-22 bytes) inside the string object. If the string is short, it stays on the stack. Once it exceeds that limit, it jumps to the heap.
Why does this matter? Because if you're designing a system with millions of tiny strings, you want to keep them under that SSO threshold to avoid millions of tiny heap allocations.
Safety vs. Speed
Lastly, let's talk about accessing characters. I usually use operator[], but let's see what happens when we go out of bounds:
std::string name = "Dev";
char c1 = name[10]; // What happens here?
char c2 = name.at(10); // And here?
When I run this, name[10] doesn't crash immediately—it just reads garbage memory (undefined behavior), which is a nightmare to debug. But name.at(10) throws a std::out_of_range exception. I've learned to use .at() during the debugging phase of a project to catch logic errors, and then switch to [] in the final release for a tiny performance boost once I'm certain the indices are correct.
📋 Practical Task
Build a Zero-Allocation Log Parser
You are given a raw log string containing multiple entries separated by pipes (|). Your task is to write a function that extracts a specific field (like "Timestamp" or "Level") without creating any new std::string objects during the parsing process.
Requirements:
- Create a function
std::string_view get_field(std::string_view log_line, int field_index). - The function should scan the
log_line, count the|delimiters, and return astring_viewpointing to the requested field. - If the
field_indexis out of range, return an emptystring_view. - Test your function with a string like:
"2023-10-01|ERROR|DatabaseTimeout|Connection lost". - Verify that no heap allocations occur during the
get_fieldcall.
There are no comments for now.