C++の正規表現処理について

AI実装検定のご案内

C++では、C++11以降、標準ライブラリの <regex> を使って正規表現処理を行えます。

std::regex を使うことで、文字列の形式チェック、部分一致検索、置換、複数マッチの抽出、簡易的な分割処理などが可能です。

ただし、C++の正規表現には注意点もあります。

特に、C++文字列リテラルにおけるバックスラッシュの扱い、std::regex の性能、Unicode処理、簡易チェックと厳密な検証の違いは重要です。

目次

C++で正規表現を扱う基本

<regex> を使う

C++で正規表現を使うには、以下のヘッダーを読み込みます。

#include <regex>
#include <string>
#include <iostream>

主に使用するクラスや関数は次のとおりです。

機能使用するもの
正規表現パターンを表すstd::regex
文字列全体が一致するか調べるstd::regex_match
文字列の一部が一致するか調べるstd::regex_search
一致した部分を置換するstd::regex_replace
マッチ結果を受け取るstd::smatch
複数の一致を順番に取得するstd::sregex_iterator
正規表現を使って分割するstd::sregex_token_iterator

std::regex_match は全体一致を調べる

std::regex_match は、対象文字列全体が正規表現に一致するかを調べる関数です。

std::string text = "abc123";
std::regex pattern(R"([a-z]+[0-9]+)");

if (std::regex_match(text, pattern)) {
    std::cout << "一致しました\n";
}

この例では、abc123 全体が [a-z]+[0-9]+ に一致しているため、判定は true になります。

一方で、次のようなコードでは一致しません。

std::string text = "abc123xyz";
std::regex pattern(R"([0-9]+)");

std::regex_match(text, pattern); // false

文字列の中に 123 という数字部分はありますが、文字列全体が数字だけではないためです。

入力値全体の形式を確認したい場合は、std::regex_match を使います。

std::regex_search は部分一致を調べる

std::regex_search は、文字列の中に正規表現に一致する部分があるかを調べる関数です。

std::string text = "abc123xyz";
std::regex pattern(R"([0-9]+)");

if (std::regex_search(text, pattern)) {
    std::cout << "数字が含まれています\n";
}

この例では、文字列の中に 123 が含まれているため、判定は true になります。

文字列全体ではなく、内部に特定のパターンが含まれているかを調べたい場合は、std::regex_search を使います。

std::smatch でマッチ結果を取得する

一致した文字列を取得したい場合は、std::smatch を使います。

std::string text = "price: 1200 yen";
std::regex pattern(R"([0-9]+)");
std::smatch match;

if (std::regex_search(text, match, pattern)) {
    std::cout << match.str() << "\n";
}

このコードでは、1200 が出力されます。

match.str()match[0].str() は、どちらもマッチ全体を表します。

match.str();
match[0].str();

キャプチャグループで一部を取り出す

正規表現の () を使うと、マッチした文字列の一部を取り出せます。

std::string text = "Date: 2026-07-04";
std::regex pattern(R"((\d{4})-(\d{2})-(\d{2}))");
std::smatch match;

if (std::regex_search(text, match, pattern)) {
    std::cout << match[0].str() << "\n"; // 2026-07-04
    std::cout << match[1].str() << "\n"; // 2026
    std::cout << match[2].str() << "\n"; // 07
    std::cout << match[3].str() << "\n"; // 04
}

match[0] は一致全体、match[1] 以降はキャプチャグループに対応します。

オプショナルなキャプチャグループを扱う場合は、matched を確認すると安全です。

if (match[1].matched) {
    std::cout << match[1].str() << "\n";
}

正規表現を書くときの注意点

通常の文字列リテラルではバックスラッシュに注意する

C++の通常文字列リテラルでは、バックスラッシュ \ はエスケープ文字として扱われます。

そのため、正規表現エンジンに \d+ を渡したい場合、C++コード上では次のように書く必要があります。

std::regex pattern("\\d+");

これは、正規表現としては次の意味になります。

\d+

つまり、1文字以上の数字に一致します。

raw string literal を使うと読みやすい

C++で正規表現を書く場合は、raw string literal を使うと読みやすくなります。

std::regex pattern(R"(\d+)");

通常の文字列リテラルでは \\d+ と書く必要がありますが、raw string literal では \d+ をそのまま書けます。

日付のような正規表現も、raw string literal を使うと見やすくなります。

std::regex datePattern(R"(\d{4}-\d{2}-\d{2})");

正規表現を扱うコードでは、基本的に raw string literal を使うとミスを減らせます。

デフォルトの正規表現文法はECMAScript

std::regex は、デフォルトでは ECMAScript 文法を使います。

std::regex pattern(R"(\d+)");

これは次のように書くのと同じです。

std::regex pattern(
    R"(\d+)",
    std::regex_constants::ECMAScript
);

文法は必要に応じて変更できます。

std::regex pattern1("...", std::regex_constants::basic);
std::regex pattern2("...", std::regex_constants::extended);
std::regex pattern3("...", std::regex_constants::awk);
std::regex pattern4("...", std::regex_constants::grep);
std::regex pattern5("...", std::regex_constants::egrep);

通常の用途では、デフォルトの ECMAScript 文法を使えば十分です。

大文字小文字を無視する

大文字小文字を区別せずにマッチしたい場合は、std::regex_constants::icase を指定します。

std::string text = "Hello";
std::regex pattern("hello", std::regex_constants::icase);

if (std::regex_match(text, pattern)) {
    std::cout << "一致しました\n";
}

より明示的に書くなら、文法とオプションを組み合わせます。

std::regex pattern(
    "hello",
    std::regex_constants::ECMAScript | std::regex_constants::icase
);

置換と複数マッチの取得

std::regex_replace で置換する

std::regex_replace を使うと、一致した部分を別の文字列に置換できます。

std::string text = "My phone number is 090-1234-5678.";
std::regex pattern(R"(\d{3}-\d{4}-\d{4})");

std::string result = std::regex_replace(text, pattern, "***-****-****");

std::cout << result << "\n";

出力は次のようになります。

My phone number is ***-****-****.

キャプチャグループを使って置換する

キャプチャグループを使うと、マッチした一部を並び替えて置換できます。

std::string text = "Today is 2026-07-04.";
std::regex pattern(R"((\d{4})-(\d{2})-(\d{2}))");

std::string result = std::regex_replace(text, pattern, "$3/$2/$1");

std::cout << result << "\n";

出力は次のようになります。

Today is 04/07/2026.

置換文字列では、$1$2$3 のようにキャプチャグループを参照できます。

表記意味
$&マッチ全体
$11番目のキャプチャ
$22番目のキャプチャ
$33番目のキャプチャ

std::sregex_iterator で複数の一致を取得する

文字列中の一致をすべて取得したい場合は、std::sregex_iterator を使います。

std::string text = "apple:100 banana:200 orange:300";
std::regex pattern(R"((\w+):(\d+))");

auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
auto end = std::sregex_iterator();

for (auto it = begin; it != end; ++it) {
    std::smatch match = *it;

    std::cout << match[1].str() << ": "
              << match[2].str() << "\n";
}

このコードでは、apple:100banana:200orange:300 を順番に処理できます。

std::regex_token_iterator で分割する

C++には標準で直接的な split 関数はありませんが、std::regex_token_iterator を使うと、正規表現を区切り文字として分割できます。

std::string text = "apple,banana;orange grape";
std::regex delimiter(R"([,;\s]+)");

std::sregex_token_iterator it(
    text.begin(),
    text.end(),
    delimiter,
    -1
);

std::sregex_token_iterator end;

for (; it != end; ++it) {
    std::cout << *it << "\n";
}

最後の引数に -1 を指定すると、区切り文字そのものではなく、区切り文字の間にある文字列を取得できます。

よく使う正規表現パターン

数字

std::regex pattern(R"(\d+)");

ASCII数字だけを明確に対象にするなら、次のように書く方法もあります。

std::regex pattern(R"([0-9]+)");

英字

std::regex pattern(R"([A-Za-z]+)");

英数字

std::regex pattern(R"([A-Za-z0-9]+)");

空白

std::regex pattern(R"(\s+)");

メールアドレスの簡易チェック

std::regex emailPattern(
    R"([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})"
);

この正規表現は簡易チェック用です。

メールアドレスの仕様は複雑なため、厳密な検証には不十分です。

URLの簡易チェック

std::regex urlPattern(
    R"(https?://[A-Za-z0-9./?=&_%+-]+)"
);

この正規表現も簡易チェック用です。

ポート番号、IPv6ホスト、日本語URL、パーセントエンコードなどを厳密に扱うには不十分です。

日付形式のチェック

std::regex datePattern(R"(\d{4}-\d{2}-\d{2})");

この正規表現は YYYY-MM-DD という形式を確認するものです。

ただし、次のような実在しない日付にも一致します。

2026-99-99

日付として妥当かどうかを確認する場合は、正規表現だけでなく日付処理と組み合わせる必要があります。

日本の電話番号の簡易チェック

std::regex phonePattern(R"(0\d{1,4}-\d{1,4}-\d{3,4})");

これは電話番号らしい形式を確認する簡易チェックです。

市外局番、市内局番、携帯番号、IP電話、フリーダイヤル、国番号付き表記などを厳密に判定するには、より細かい処理が必要です。

IPv4アドレスの簡易抽出

std::regex ipPattern(R"((\d{1,3}\.){3}\d{1,3})");

この正規表現は 192.168.0.1 のような文字列を抽出する用途には使えます。

ただし、次のような不正な値にも一致します。

999.999.999.999

IPv4として妥当かどうかを確認するには、より厳密なパターンが必要です。

IPv4アドレスの厳密チェック

std::regex ipv4Pattern(
    R"((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})"
);

このパターンは、各オクテットを 0255 の範囲に制限します。

ただし、01.02.03.04 のような先頭ゼロ付き表記は許可しません。

先頭ゼロを許すかどうかは、要件に応じて決める必要があります。

エラー処理

不正な正規表現では std::regex_error が発生する

不正な正規表現を指定すると、std::regex_error 例外が発生します。

try {
    std::regex pattern("(");
} catch (const std::regex_error& e) {
    std::cout << "regex error: " << e.what() << "\n";
    std::cout << "error code: " << e.code() << "\n";
}

ユーザーが入力した正規表現を使う場合は、例外処理を入れておくべきです。

パフォーマンスに関する注意点

std::regex オブジェクトは使い回す

正規表現オブジェクトの構築にはコストがかかります。

同じ正規表現を繰り返し使う場合は、毎回作るのではなく使い回すべきです。

悪い例は次のようなコードです。

for (const auto& line : lines) {
    std::regex pattern(R"(\d+)");
    if (std::regex_search(line, pattern)) {
        // ...
    }
}

このコードでは、ループのたびに std::regex オブジェクトが作られます。

より良い書き方は次のとおりです。

std::regex pattern(R"(\d+)");

for (const auto& line : lines) {
    if (std::regex_search(line, pattern)) {
        // ...
    }
}

関数内で固定の正規表現を使う場合は、static const にする方法もあります。

bool hasNumber(const std::string& s) {
    static const std::regex pattern(R"(\d+)");
    return std::regex_search(s, pattern);
}

std::regex_constants::optimize は過信しない

std::regex_constants::optimize を指定すると、マッチング速度を優先するよう実装に指示できます。

std::regex pattern(
    R"(\d+)",
    std::regex_constants::ECMAScript | std::regex_constants::optimize
);

ただし、必ず高速化するわけではありません。

正規表現オブジェクトの構築コストが増える可能性もあります。

同じ正規表現を何度も使い回す場合に検討する程度で十分です。

std::regex は実装によって性能差がある

std::regex は標準ライブラリの機能ですが、実装によって性能差が出ることがあります。

実装
libstdc++GCCで使われることが多い
libc++Clangで使われることが多い
MSVC STLVisual C++で使われる

大量ログの解析、高頻度な入力検証、サーバーサイドでの大量処理などでは、std::regex が十分に速いかどうかをベンチマークするべきです。

単純な処理では正規表現を使わない方がよい場合もある

特定の文字列が含まれているか調べるだけなら、find で十分です。

if (s.find("ERROR") != std::string::npos) {
    // ERRORを含む
}

C++20以降で前方一致を調べるなら、starts_with を使えます。

if (s.starts_with("ERROR")) {
    // ERRORで始まる
}

数字だけで構成されているかを調べる場合も、正規表現を使わずに書けます。

bool allDigits(const std::string& s) {
    return !s.empty() &&
           std::all_of(s.begin(), s.end(), [](unsigned char c) {
               return std::isdigit(c);
           });
}

単純な文字列処理では、正規表現を使わない方が速く、読みやすいことがあります。

破滅的バックトラッキングに注意する

次のような正規表現は、入力によって非常に遅くなる可能性があります。

(a+)+$

たとえば、次のような文字列に対して処理が重くなることがあります。

aaaaaaaaaaaaaaaaaaaa!

このような現象は、破滅的バックトラッキングと呼ばれます。

ユーザーが入力した正規表現をそのまま使う場合や、大量データに対して複雑な正規表現を適用する場合は注意が必要です。

安全性や線形時間性を重視する場合は、RE2のようなライブラリを検討することもあります。

Unicode・日本語処理に関する注意点

標準の std::regex はUnicode正規表現に強くない

C++標準の std::regex は、Unicode処理に特化した正規表現エンジンではありません。

たとえば、次のようなUnicodeプロパティを使った正規表現は、標準の std::regex では一般的には期待どおりに動きません。

\p{Hiragana}
\p{Katakana}
\p{Han}

日本語の文字種を厳密に判定したい場合には注意が必要です。

std::wregex でUnicode問題がすべて解決するわけではない

ワイド文字列を扱う場合は、std::wregexstd::wsmatch を使えます。

std::wstring text = L"abc123";
std::wregex pattern(LR"(\d+)");
std::wsmatch match;

ただし、std::wregex を使えばUnicode正規表現機能が完全に扱える、という意味ではありません。

Unicodeプロパティ、正規化、書記素クラスタ、日本語の文字種判定などを本格的に扱う場合は、専用ライブラリを検討するべきです。

日本語処理には専用ライブラリを検討する

日本語やUnicodeを本格的に扱う場合は、標準の <regex> だけで処理しようとしない方が安全です。

用途に応じて、次のようなライブラリを検討します。

ライブラリ向いている用途
ICUUnicodeプロパティ、正規化、文字種判定
PCRE2高機能な正規表現
Boost.Regex標準以上の正規表現機能が必要な場合
RE2高速性、安全性、ReDoS対策を重視する場合

特に日本語テキストでは、文字コード、正規化、全角・半角、濁点・半濁点、結合文字なども関係します。

正規表現が向いている処理

形式チェック

正規表現は、入力文字列が特定の形式に合っているかを確認する処理に向いています。

たとえば、次のような用途です。

郵便番号の形式チェック
日付形式のチェック
ID形式のチェック
簡易的なメールアドレスチェック
簡易的な電話番号チェック

ただし、形式が合っていることと、値として妥当であることは別です。

たとえば、2026-99-99 は日付形式には見えますが、実際の日付としては不正です。

簡単な抽出

ログや文章から特定のパターンを抜き出す処理にも正規表現は向いています。

ログからIPアドレスを抽出する
文章からURLを抽出する
テキストから数値を取り出す
エラーメッセージからコードを取り出す

このような処理では、std::regex_searchstd::sregex_iterator が役立ちます。

単純な置換

一致した部分をまとめて置換する処理にも向いています。

電話番号をマスクする
複数の空白を1つにまとめる
日付形式を変換する
不要な記号を削除する

たとえば、複数の空白を1つにまとめる場合は次のように書けます。

std::string text = "Hello     world   from   C++";
std::regex spaces(R"(\s+)");

std::string result = std::regex_replace(text, spaces, " ");

正規表現が向いていない処理

HTML・XML・JSONの完全な解析

HTML、XML、JSONのような構造化データを正規表現だけで完全に解析するのは避けるべきです。

これらのデータには、入れ子構造、エスケープ、属性、コメント、引用符などが含まれます。

簡単な一時処理であれば正規表現を使うこともありますが、本格的に扱う場合は専用パーサーを使うべきです。

CSVの厳密な解析

CSVも、一見するとカンマで分割するだけに見えます。

しかし、次のようなデータでは単純な分割では壊れます。

"apple, red",banana,orange

引用符内のカンマを正しく扱うには、CSVの仕様に対応したパーサーが必要です。

プログラミング言語や複雑な構文の解析

プログラミング言語、SQL、Markdownなどのように複雑な文法を持つものを正規表現だけで解析するのは難しいです。

このような場合は、字句解析器や構文解析器、専用ライブラリを使う方が安全です。

実務での使い分け

regex_match を使う場面

regex_match は、入力全体が特定の形式に一致するかを調べる場合に使います。

std::regex_match(input, pattern);

たとえば、次のような場面です。

入力全体が郵便番号か確認する
入力全体が整数か確認する
入力全体が日付形式か確認する
入力全体がID形式か確認する

regex_search を使う場面

regex_search は、文字列の中に特定のパターンが含まれているかを調べる場合に使います。

std::regex_search(text, pattern);

たとえば、次のような場面です。

ログの中にエラーコードが含まれているか調べる
文章の中にURLが含まれているか調べる
テキストの中から最初の数値を探す

sregex_iterator を使う場面

std::sregex_iterator は、文字列中のすべての一致を順番に処理したい場合に使います。

auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
auto end = std::sregex_iterator();

たとえば、次のような場面です。

ログ中のすべてのIPアドレスを取得する
文章中のすべてのURLを取得する
テキスト内のすべての数値を抽出する

regex_replace を使う場面

std::regex_replace は、一致した部分を別の文字列に置換する場合に使います。

std::regex_replace(text, pattern, replacement);

たとえば、次のような場面です。

電話番号を伏せ字にする
日付形式を変換する
複数空白を1つにする
特定の文字列パターンを削除する

C++の正規表現処理のまとめ

C++の正規表現処理では、まず以下の使い分けを理解することが重要です。

やりたいこと使うもの
文字列全体の形式を確認するstd::regex_match
文字列中にパターンがあるか確認するstd::regex_search
一致した部分を取得するstd::smatch
複数の一致を取得するstd::sregex_iterator
一致部分を置換するstd::regex_replace
正規表現で分割するstd::regex_token_iterator

C++の <regex> は、形式チェック、簡単な抽出、単純な置換には便利です。

一方で、Unicode処理、高頻度処理、大量データ処理、複雑な構文解析では注意が必要です。

特に、HTML、JSON、CSV、XMLなどを厳密に扱う場合は、正規表現だけに頼らず、専用パーサーを使うべきです。

実務では、正規表現を万能な解析手段として使うのではなく、用途を絞って使うことが重要です。

regex_matchregex_searchregex_replacesregex_iterator を適切に使い分けることで、C++でも読みやすく実用的な文字列処理を実装できます。

以上、C++の正規表現処理についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次