Summary: C++11에서 도입된 <regex>의 구성과 사용법을 정리한다. 컴파일된 정규식 객체와 세 알고리즘 함수(match/search/replace), 문법 선택(기본값 ECMAScript)과 ECMAScript 문법 요소, 매칭·치환 플래그, 매치 결과(smatch) 접근, 반복자 기반 다중 매치·분할, 예외 계층, 그리고 이스케이프·컴파일 비용·백트래킹 같은 실무 한계를 다룬다.
정규 표현식(Regular Expression)은 문자열의 패턴을 기술하는 형식 언어로, 매칭·검색·치환에 사용된다. <regex>는 이 기능을 몇 개의 축으로 나눠 제공한다. 컴파일된 정규식 객체(basic_regex), 알고리즘 함수(match/search/replace), 매치 결과(match_results·sub_match), 순회 반복자(regex_iterator 등), 예외(regex_error), 그리고 문자 특성(regex_traits)이다. 각 컴포넌트는 char용과 wchar_t용(w- 접두)의 두 계열 별칭으로 제공된다.
핵심 컴포넌트
라이브러리는 "패턴을 담는 객체 → 매치를 수행하는 함수 → 결과를 담는 객체 → 결과를 순회하는 반복자 → 오류를 알리는 예외"로 역할이 나뉜다.
| 컴포넌트 | 별칭 | 역할 |
| basic_regex | regex(char), wregex(wchar_t) | 컴파일된 정규식 객체, 생성 시 문법·수정자(syntax_option_type) 지정 |
| sub_match | ssub_match, csub_match | 하위식이 매치한 문자 구간 [first, last); 멤버 matched/first/last/str()/length() |
| match_results | smatch, cmatch, wsmatch, wcmatch | 한 번의 매치 결과 + 모든 하위식 매치 모음 |
| regex_iterator | sregex_iterator, cregex_iterator | 시퀀스 내 모든 매치를 순차 순회 |
| regex_token_iterator | — | 매치의 지정 하위식들 또는 미매치 조각을 순회(-1이면 split) |
| regex_error | — | 라이브러리 오류 예외, code()로 error_type 조회 |
| regex_traits | — | 문자 타입 메타정보 제공(로캘/문자 분류) |
알고리즘 함수
세 함수는 "어디까지 일치를 요구하느냐"로 갈린다.
- regex_match(대상, [결과,] re, [flags]): 대상 전체가 정규식과 완전히 일치할 때만 true이며, 부분 일치는 false다.
- regex_search(대상, [결과,] re, [flags]): 대상의 일부(부분 문자열)에서 첫 매치를 탐색한다. 존재 여부 판정과 추출에 쓴다.
- regex_replace(대상, re, 포맷, [flags]): 매치를 포맷 문자열로 치환해 반환한다.
#include <regex>
std::regex re(R"(\d+)"); // 하나 이상의 숫자
std::smatch m;
std::string s = "id=42, x=7";
bool full = std::regex_match(s, re); // false (전체 불일치)
bool found = std::regex_search(s, m, re); // true, m[0]="42"
std::string out = std::regex_replace(s, re, "#"); // "id=#, x=#"
regex_match가 false인 이유는 대상 전체가 숫자만으로 이뤄져 있지 않기 때문이고, regex_search는 첫 숫자열 "42"를 찾아낸다. regex_replace는 매치된 모든 숫자열을 #으로 바꾼다. 결과 인자가 없는 오버로드는 매치 여부(bool)만 필요할 때 쓰며, 대상은 std::string·C 문자열·반복자 쌍 모두 가능하다(별칭이 각각 대응한다).
문법 선택 (syntax_option_type)
syntax_option_type은 basic_regex 생성 시 넘기는 BitmaskType로, 문법 하나와 수정자들의 조합으로 구성된다. 문법 선택자(ECMAScript/basic/extended/awk/grep/egrep)는 최대 하나만 지정할 수 있고, 아무것도 지정하지 않으면 ECMAScript가 선택된다. 그래서 std::regex("meow", std::regex::icase)는 std::regex("meow", std::regex::ECMAScript|std::regex::icase)와 같다.
문법 선택자는 다음과 같다.
- ECMAScript: 수정된 ECMAScript 문법으로 기본값이다. JavaScript/.NET에 가장 근접하며 비탐욕과 역참조를 지원한다.
- basic: POSIX 기본 정규식(BRE).
- extended: POSIX 확장 정규식(ERE).
- awk: POSIX awk 문법으로, extended와 동일하되 C 스타일 이스케이프 시퀀스를 허용한다.
- grep: POSIX grep(basic 계열)으로, 개행(\n)을 교대(alternation) 구분자로 허용한다.
- egrep: POSIX grep -E(extended 계열)으로, 개행을 교대 구분자로 허용한다.
수정자 플래그는 문법과 OR로 조합한다.
- icase: 대소문자 구분 없이 매칭.
- nosubs: 마킹된 하위식을 비마킹으로 취급해 하위 매치를 저장하지 않음.
- optimize: 생성(컴파일) 속도보다 매칭 속도를 우선.
- collate: [a-b] 같은 범위 비교가 로캘 의존.
- multiline(C++17~): ECMAScript 한정으로, ^/$가 입력 전체뿐 아니라 각 줄 시작/끝에도 매칭.
기본값이 ECMAScript라는 선택은 실용적 함의가 있다. 비탐욕과 역참조를 지원하는 가장 표현력 높은 문법이 별도 지정 없이 적용되기 때문이다. POSIX 계열은 각 도구(awk/grep 등)의 관행을 따르려는 경우에 선택한다.
ECMAScript 문법 요소
아래 요소는 기본값인 ECMAScript 기준이다.
- 앵커: ^(시작), $(끝), \b(단어 경계), \B(비단어 경계)
- 문자 클래스 약어: \d/\D(숫자/비숫자), \w/\W(단어문자[영숫자+_]/비단어), \s/\S(공백/비공백)
- .: 개행을 제외한 임의의 1문자
- 대괄호식: [abc](집합 중 하나), [^abc](부정), [a-z](범위)
- 수량자: *(0회 이상), +(1회 이상), ?(0/1회), {n}(정확히 n), {n,}(n 이상), {n,m}(n~m). 뒤에 ?를 붙이면 비탐욕(lazy): *?, +?, ??, {n,m}?
- 그룹: (…)(캡처 그룹), (?:…)(비캡처 그룹, 그룹핑만)
- 교대: |(최저 우선순위)
- 역참조: \1…\9(앞선 캡처 그룹이 매치한 문자열 재사용)
- 이스케이프: \ . * + ? { } [ ] ( ) | ^ $는 백슬래시로 이스케이프
매칭 규칙에는 두 가지 유의점이 있다. 수량자는 기본이 탐욕(greedy)이라 가능한 최대까지 소비한 뒤 실패하면 백트래킹한다. 그리고 ECMAScript는 |에서 먼저 매치되는 왼쪽 대안(leftmost)을 선택하는 반면, POSIX 계열은 leftmost-longest(최장 일치)를 택한다. 이 차이 때문에 마크업 파싱처럼 "가장 가까운 닫는 태그까지만" 잡아야 하는 경우 ECMAScript의 비탐욕(.*?)이 적합하다.
매칭 플래그 (match_flag_type)
알고리즘 호출 시 매칭 동작을 세부 조정하는 플래그다.
매칭 제어는 다음과 같다.
- match_default: 기본 동작(빈 비트마스크)
- match_not_bol / match_not_eol: 대상의 첫/끝 문자를 줄 시작/끝이 아닌 것으로 취급(^/$에 영향)
- match_not_bow / match_not_eow: \b가 대상 시작/끝 경계에서 매치하지 않음
- match_any: 여러 매치가 가능하면 아무거나 허용
- match_not_null: 빈 시퀀스는 매치하지 않음
- match_continuous: 대상 시작(first)에서 시작하는 부분만 매치
- match_prev_avail: --first가 유효하며, 이때 match_not_bol·match_not_bow는 무시된다
치환 포맷 플래그(regex_replace)는 다음과 같다.
- format_default: ECMAScript 치환 규칙($&=전체, $1…=그룹, $`=앞부분, $'=뒷부분)
- format_sed: POSIX sed 규칙
- format_no_copy: 미매치 부분을 출력에 복사하지 않음
- format_first_only: 첫 매치만 치환
경계 관련 플래그들은 대상 문자열이 더 큰 텍스트의 일부일 때 의미가 있다. 예컨대 match_prev_avail은 대상 앞에 유효한 문자가 있음을 알려 앞 경계 처리를 정상화하며, 이때 match_not_bol/match_not_bow가 무시된다.
match_results / smatch 접근
매치 결과의 인덱스 규칙은 명확하다. 0번은 전체 매치, 1..n은 캡처 그룹이며, 그룹 번호는 정규식의 여는 괄호 순서를 따른다.
- empty(): 매치에 실패하면 true
- size(): 하위 매치 개수(전체 매치 포함)
- operator[](n) / str(n) / length(n) / position(n): n번째 sub_match 접근·문자열·길이·시작 위치
- prefix() / suffix(): 매치 앞/뒤의 미매치 구간(sub_match)
- begin() / end(): sub_match 순회
- format(포맷): 매치 결과로 포맷 문자열 생성
std::regex re(R"((\d{4})-(\d{2})-(\d{2}))"); // 연-월-일
std::smatch m;
std::string s = "d=2026-07-01;";
if (std::regex_search(s, m, re)) {
m.str(0); // "2026-07-01" (전체)
m.str(1); // "2026" (그룹1)
m.position(1); // 그룹1 시작 위치
m.prefix().str();// "d="
m.suffix().str();// ";"
}
세 개의 괄호가 각각 그룹 1·2·3이 되고, m.str(0)은 전체 매치를, m.str(1)은 첫 그룹을 돌려준다. prefix()/suffix()는 매치되지 않은 앞뒤 구간을 담으므로, 원문에서 매치가 어디에 위치했는지까지 함께 파악할 수 있다.
반복자로 다중 매치·분할
한 번의 regex_search는 첫 매치만 다루므로, 여러 매치를 순회하거나 문자열을 분할하려면 반복자를 쓴다.
- regex_iterator: 대상 내 모든 매치를 차례로 방문하며, end 반복자까지 순회한다.
- regex_token_iterator: 각 매치의 지정 그룹을 뽑거나, -1을 지정하면 매치 사이의 조각을 뽑아 split을 구현한다.
std::string s = "a=1, b=2, c=3";
std::regex re(R"(\d+)");
for (std::sregex_iterator it(s.begin(), s.end(), re), end; it != end; ++it)
(*it).str(); // "1","2","3" 순회
std::regex sep(R"(\s*,\s*)");
std::sregex_token_iterator tk(s.begin(), s.end(), sep, -1), end;
// tk 순회 => "a=1","b=2","c=3" (구분자 사이 토큰)
앞의 반복자는 숫자열 매치 자체를 순회하고, 뒤의 토큰 반복자는 구분자 정규식(sep)을 기준으로 그 사이의 조각을 돌려준다. -1 인자가 "매치가 아니라 매치 사이"를 가리키게 하여 분할을 표현한다.
예외 (regex_error / error_type)
잘못된 정규식이나 매칭 실패는 std::regex_error로 던져지며, code()가 구체적 원인을 담은 error_type을 반환한다.
error_type 의미
| error_collate | 잘못된 collating 요소 이름 |
| error_ctype | 잘못된 문자 클래스 이름 |
| error_escape | 잘못된 이스케이프 문자 또는 끝에 오는 백슬래시 |
| error_backref | 잘못된 역참조 |
| error_brack | [ ] 짝 불일치 |
| error_paren | ( ) 짝 불일치 |
| error_brace | { } 짝 불일치 |
| error_badbrace | {} 안의 잘못된 범위 |
| error_range | 잘못된 문자 범위 (예: [b-a]) |
| error_space | 정규식을 상태기계로 변환할 메모리 부족 |
| error_badrepeat | * ? + { 앞에 유효한 정규식이 없음 |
| error_complexity | 매칭 복잡도가 사전 한도 초과 |
| error_stack | 매칭 수행 메모리 부족 |
코드들은 크게 두 부류로 읽힌다. 대부분은 패턴 문법 자체의 오류(괄호·중괄호·대괄호 짝, 범위, 이스케이프, 역참조, 반복 지정)이고, error_space·error_complexity·error_stack은 문법이 아니라 변환/매칭 과정의 리소스·복잡도 한계에서 발생한다.
주의 / 한계
- C++ 문자열 리터럴에서의 이스케이프: 일반 리터럴은 백슬래시를 두 번 써야 하고("\\d+"), 이를 피하려면 raw string R"(\d+)"을 쓰는 편이 낫다.
- 정규식 컴파일 비용: optimize가 생성 속도와 매칭 속도를 맞바꾸는 데서 보이듯 컴파일에는 비용이 있으므로, 반복 매칭 시 regex 객체를 루프 밖에서 만들어 재사용한다.
- 탐욕 기본값: 의도치 않은 최장 매치에 주의하고, 필요하면 비탐욕(?)을 쓴다.
- 과도한 백트래킹: 복잡한 패턴과 큰 입력에서는 error_complexity/error_stack이 발생할 수 있다.
- 문법 선택자는 2개 이상 동시 지정이 금지되며(최대 1개), 미지정 시 ECMAScript가 적용된다.
- multiline은 ECMAScript 전용이고 C++17부터 제공된다.
'C++ 스터디' 카테고리의 다른 글
| C++ 스마트 포인터에 값 입력받기 (0) | 2026.07.03 |
|---|---|
| STL 컬렉션, 예외 처리, 파일 직렬화 (0) | 2026.06.30 |
| C++20 chrono로 날짜·시간 다루기 (0) | 2026.06.19 |
| C++ 함수의 기술적 사용: 선언·정의 분리, 인자 전달, 함수 포인터 (0) | 2026.06.17 |