Substring Search in C++: From Naivety to Performance

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Foreword: Why "just find()" is not always the answer

\n\n

Imagine a scenario: you are writing a log parser processing millions of lines per second, or a text search engine. The first thing that comes to mind is to call std::string::find(). "It works? It works. What else do you need?" — thinks 90% of programmers. And they are right... until the moment when 10 milliseconds decide the fate of the entire application.

\n\n

The question "Check if a string contains a string in C++" on Stack Overflow has gathered hundreds of thousands of views. And this is no coincidence. This seemingly trivial task hides many pitfalls: from choosing the search algorithm to correctly using modern C++17 and C++20 features.

\n\n

In this article, we will not just show how to check if a string contains a substring. We will analyze 7 ways to do this, compare their performance on real data, and give clear recommendations for different scenarios. Let's go!

\n\n

The Classic: std::string::find()

\n\n

Let's start with the basics that everyone knows. The find() method returns the position of the first occurrence of the substring or the special value std::string::npos if nothing is found.

\n\n
#include <iostream>\n#include <string>\n\nint main() {\n    std::string haystack = "Hello, C++ developer!";\n    std::string needle = "C++";\n    \n    if (haystack.find(needle) != std::string::npos) {\n        std::cout << "Substring found!" << std::endl;\n    }\n    \n    // Search with a specified starting position\n    size_t pos = haystack.find("C++", 5); // start from the 5th character\n    return 0;\n}
\n\n

Pros: simplicity, readability, built into STL.
\nCons: uses a naive search algorithm (O(n*m) in the worst case). Ideal for short strings, but can be slow for long ones.

\n\n

When is find() evil?

\n

If you are searching for a substring in a string of length 105+ characters, and the substring is long and repetitive (e.g., "aaaaaab" in "aaaaaaaaaaaaaaaaaaaaab"), the naive algorithm starts to degrade. In such cases, find() can be 10–100 times slower than optimized alternatives.

\n\n

string_view: Lightweight and Fast

\n\n

With the arrival of C++17, we got std::string_view — a non-owning view of a string. It is ideal for functions that only read data without copying it.

\n\n
#include <iostream>\n#include <string_view>\n\nbool contains(std::string_view haystack, std::string_view needle) {\n    return haystack.find(needle) != std::string_view::npos;\n}\n\nint main() {\n    std::string_view text = "Optimization in C++ is an art";\n    std::string_view pattern = "art";\n    \n    if (contains(text, pattern)) {\n        std::cout << "Found!" << std::endl;\n    }\n    return 0;\n}
\n\n

Why is this faster? When passing a std::string to a function, copying occurs (if not passed by reference). string_view simply copies a pointer and a length — no allocations. For frequent contains checks, this gives a performance boost of up to 30–40%.

\n\n

Algorithmic Upgrade: Boyer-Moore and KMP

\n\n

If you are working with truly large data, it's time to remember the classic substring search algorithms. In C++17, STL search algorithms appeared:

\n\n
    \n
  • std::boyer_moore_searcher — one of the fastest for long patterns
  • \n
  • std::boyer_moore_horspool_searcher — a simplified version, faster on short patterns
  • \n
  • std::default_searcher — a wrapper over naive search
  • \n
\n\n
#include <iostream>\n#include <string>\n#include <algorithm>\n#include <functional>\n\nint main() {\n    std::string text = "Search algorithms in C++ can be very fast";\n                

Blogs

Book Recommendations