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
203: Regular Expressions with <regex>
I've seen a lot of developers treat std::regex as if it's just a fancy version of std::string::find(). They think that since they're passing a string pattern into a function, the "work" happens during the search. This leads to a very common, and very expensive, performance mistake.
The "Just Put It in the Loop" Performance Trap
Imagine you're processing a log file with ten thousand lines, and you want to check if each line contains a specific error code pattern, like ERR_[0-9]{4}. A common instinct is to do this:
for (const auto& line : log_lines) {
if (std::regex_search(line, std::regex("ERR_[0-9]{4}"))) {
// Handle error
}
}
This looks clean, but it's a disaster. In C++, std::regex doesn't just "hold" the string; it compiles that string into a complex finite state machine at runtime. By putting the constructor inside the loop, you are recompiling that state machine ten thousand times. I've seen this tank the performance of otherwise efficient tools, turning a millisecond operation into seconds of CPU churn.
Hoisting the Compiled Pattern for Speed
The fix is simple: compile your regex once and reuse it. You want to move the std::regex object outside the loop so the heavy lifting happens only once.
// Compile once, use many times
const std::regex error_pattern("ERR_[0-9]{4}");
for (const auto& line : log_lines) {
if (std::regex_search(line, error_pattern)) {
// Handle error
}
}
Now, the loop only performs the matching logic, which is where the actual efficiency of the state machine kicks in. It's a small change, but in production C++, it's the difference between a professional implementation and a bottleneck.
Extracting Specific Data with Capture Groups
Matching a string is fine, but usually, you don't just want to know if a pattern exists—you want to pull specific pieces of data out of it. This is where capture groups (the parentheses in your regex) and std::smatch come into play.
Let's say you have a string like "User: jdoe, ID: 12345" and you want both the username and the ID. You can wrap the parts you care about in parentheses:
std::string input = "User: jdoe, ID: 12345";
std::regex user_regex("User: ([^,]+), ID: ([0-9]+)");
std::smatch matches;
if (std::regex_search(input, matches, user_regex)) {
// matches[0] is the entire matched string
// matches[1] is the first capture group
// matches[2] is the second capture group
std::string username = matches[1].str();
std::string id = matches[2].str();
}
I like to think of std::smatch as a container that holds the "results" of the search. Just remember that matches[0] is always the full match; your actual captured groups start at index 1.
Cleaning Up Strings with regex_replace
Finally, there's std::regex_replace. This is my go-to for data sanitization. Instead of manually slicing strings with substr and find, you can swap out patterns globally. If you're scrubbing sensitive data—like replacing all sequences of digits with asterisks—this is the most readable way to do it.
std::string sensitive_data = "My phone number is 555-123-4567";
std::regex digit_pattern("[0-9]");
std::string scrubbed = std::regex_replace(sensitive_data, digit_pattern, "*");
// Result: "My phone number is ***-***-****"
One quick tip: C++ uses ECMAScript syntax by default. If you've used regex in JavaScript or Python, you'll feel right at home. Just be mindful of those double backslashes (\\d instead of \d) because C++ treats backslashes in string literals as escape characters.
📋 Practical Task
Building a System Log Parser
You've been handed a raw log file where each entry follows this strict format: [YYYY-MM-DD] LEVEL: Message (e.g., [2023-10-12] ERROR: Database connection failed). Your task is to write a program that filters these logs.
Requirements:
- Create a
std::vector<std::string>containing at least five log entries, including a mix ofINFO,WARNING, andERRORlevels. - Define a single
std::regexobject outside of your loop that captures three groups: the date, the level, and the message. - Iterate through the logs and use
std::regex_searchandstd::smatchto extract the data. - Only print the output if the level is
ERROR. The output should be formatted as:"Critical Issue on [Date]: [Message]".
Constraints: Ensure you do not instantiate the std::regex object inside the loop to avoid the performance trap discussed in the lesson.
There are no comments for now.