[2026] C++ Google RE2 정규표현식 완벽 가이드 | std::regex vs RE2 성능 비교

[2026] C++ Google RE2 정규표현식 완벽 가이드 | std::regex vs RE2 성능 비교

이 글의 핵심

C++ 정규표현식 완벽 가이드. std::regex 기본부터 Google RE2까지. regex_match, regex_search, regex_replace 실전 예제와 성능 최적화

🎯 이 글을 읽으면 (읽는 시간: 20분)

TL;DR: C++ 정규표현식을 완벽하게 마스터합니다. std::regex 기본부터 Google RE2 성능 최적화까지, 실전에서 바로 쓸 수 있는 패턴 매칭 기법을 배웁니다. 이 글을 읽으면:

  • ✅ regex_match, regex_search, regex_replace 완벽 이해
  • ✅ 정규표현식 패턴 작성 및 디버깅 능력 습득
  • ✅ Google RE2로 성능 10배 향상 방법 습득 실무 활용:
  • 🔥 이메일/전화번호 유효성 검사
  • 🔥 로그 파일 파싱 및 분석
  • 🔥 텍스트 데이터 추출 및 변환 난이도: 중급 | 실습 코드: 12개 | 성능 비교: Google RE2 vs std::regex

들어가며

C++11 <regex>는 정규 표현식을 지원하는 표준 라이브러리입니다. 패턴 매칭, 검색, 치환 등을 통해 문자열 처리를 강력하고 유연하게 수행할 수 있습니다.

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. Regex 기본

기본 사용

정규 표현식으로 이메일 형식을 검증하는 간단한 예제입니다: 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
#include <string>
int main() {
    std::string text = "hello@example.com";
    
    // 정규 표현식 패턴 생성
    // R"(...)" : Raw 문자열 리터럴 (이스케이프 불필요)
    // (\w+) : 첫 번째 캡처 그룹 - 하나 이상의 단어 문자 (알파벳, 숫자, _)
    // @ : 리터럴 @ 문자
    // (\w+\.\w+) : 두 번째 캡처 그룹 - 도메인 (예: example.com)
    //   \w+ : 하나 이상의 단어 문자
    //   \. : 리터럴 점 (. 은 특수문자이므로 이스케이프 필요)
    //   \w+ : 하나 이상의 단어 문자
    std::regex pattern{R"((\w+)@(\w+\.\w+))"};
    
    // regex_match: 전체 문자열이 패턴과 일치하는지 확인
    // 반환값: bool (일치하면 true, 아니면 false)
    if (std::regex_match(text, pattern)) {
        std::cout << "이메일 형식입니다" << std::endl;
    }
    
    return 0;
}

패턴 해석:

  • \w : 단어 문자 (a-z, A-Z, 0-9, _)
  • + : 1개 이상 반복
  • \. : 리터럴 점 (이스케이프 필요)
  • () : 캡처 그룹 (나중에 추출 가능)

Raw 문자열 리터럴

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
// ❌ 이스케이프 지옥
std::regex pattern1{"\\d+\\.\\d+"};
// ✅ Raw 문자열 (권장)
std::regex pattern2{R"(\d+\.\d+)"};
// 복잡한 패턴
std::regex email{R"(^[\w\.-]+@[\w\.-]+\.\w+$)"};

2. Regex 함수

regex_match (전체 매치)

regex_match전체 문자열이 패턴과 정확히 일치해야 true를 반환합니다: 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
int main() {
    std::string text1 = "123";
    std::string text2 = "abc123";
    
    // \d+ : 하나 이상의 숫자
    std::regex pattern{R"(\d+)"};
    
    // text1 = "123" : 전체가 숫자 → 매치 성공
    std::cout << std::regex_match(text1, pattern) << std::endl;  // 1 (true)
    
    // text2 = "abc123" : "abc"가 있어서 전체가 숫자가 아님 → 매치 실패
    std::cout << std::regex_match(text2, pattern) << std::endl;  // 0 (false)
    
    // regex_match는 문자열 처음부터 끝까지 전체가 패턴과 일치해야 함
    // 부분 일치는 인정하지 않음
    
    return 0;
}

사용 시나리오:

  • 입력 검증: 전화번호, 이메일, 우편번호 등이 정확한 형식인지 확인
  • 파일 확장자 검사: 파일명 전체가 특정 패턴인지 확인

regex_search (부분 매치)

regex_search는 문자열 어딘가에 패턴이 있으면 true를 반환합니다: 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
int main() {
    std::string text = "C++ 2026";
    
    // \d+ : 하나 이상의 숫자
    std::regex pattern{R"(\d+)"};
    
    // "C++ 2026"에서 "2026" 부분이 패턴과 일치 → 성공
    if (std::regex_search(text, pattern)) {
        std::cout << "숫자 발견" << std::endl;  // 출력됨
    }
    
    // regex_search는 문자열 전체를 순회하며 패턴을 찾음
    // 처음 발견된 매치를 반환
    // 여러 매치를 찾으려면 sregex_iterator 사용
    
    return 0;
}

regex_match vs regex_search 비교:

함수동작예시
regex_match전체 문자열이 패턴과 일치"123"\d+ → true
"abc123"\d+ → false
regex_search부분 문자열이 패턴과 일치"123"\d+ → true
"abc123"\d+ → true
사용 시나리오:
  • 로그 파싱: 로그 파일에서 에러 메시지 찾기
  • 텍스트 검색: 문서에서 특정 패턴 찾기
  • 데이터 추출: HTML/XML에서 태그 내용 추출

regex_replace (치환)

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
int main() {
    std::string text = "Hello World 2026";
    std::regex pattern{R"(\d+)"};
    
    // 숫자를 [숫자]로 치환
    std::string result = std::regex_replace(text, pattern, "[$&]");
    std::cout << result << std::endl;  // Hello World [2026]
    
    return 0;
}

3. 캡처 그룹

기본 캡처

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
int main() {
    std::string text = "2026-03-29";
    std::regex pattern{R"((\d{4})-(\d{2})-(\d{2}))"};
    
    std::smatch matches;
    if (std::regex_match(text, matches, pattern)) {
        std::cout << "전체: " << matches[0] << std::endl;  // 2026-03-29
        std::cout << "년: " << matches[1] << std::endl;    // 2026
        std::cout << "월: " << matches[2] << std::endl;    // 03
        std::cout << "일: " << matches[3] << std::endl;    // 29
    }
    
    return 0;
}

이름있는 캡처 (C++11에서는 미지원)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
int main() {
    std::string text = "user@example.com";
    std::regex pattern{R"((\w+)@(\w+\.\w+))"};
    
    std::smatch matches;
    if (std::regex_match(text, matches, pattern)) {
        std::string username = matches[1];
        std::string domain = matches[2];
        
        std::cout << "사용자: " << username << std::endl;  // user
        std::cout << "도메인: " << domain << std::endl;    // example.com
    }
    
    return 0;
}

4. 반복자 (Iterator)

모든 매치 찾기

#include <regex>
#include <iostream>
#include <string>
int main() {
    std::string text = "C++ 11, C++ 14, C++ 17, C++ 20";
    std::regex pattern{R"(C\+\+ (\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.str() << std::endl;
        std::cout << "버전: " << match[1] << std::endl;
    }
    
    // 출력:
    // 전체: C++ 11
    // 버전: 11
    // 전체: C++ 14
    // 버전: 14
    // ...
    
    return 0;
}

토큰 반복자

#include <regex>
#include <iostream>
int main() {
    std::string text = "apple,banana,cherry";
    std::regex pattern{R"(,)"};
    
    // 구분자로 분리
    std::sregex_token_iterator begin(text.begin(), text.end(), pattern, -1);
    std::sregex_token_iterator end;
    
    for (auto it = begin; it != end; ++it) {
        std::cout << *it << std::endl;
    }
    
    // 출력:
    // apple
    // banana
    // cherry
    
    return 0;
}

5. 실전 예제

예제 1: 이메일 검증

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
#include <string>
bool isValidEmail(const std::string& email) {
    // 이메일 패턴 (간단 버전)
    std::regex pattern{R"(^[\w\.-]+@[\w\.-]+\.\w{2,}$)"};
    return std::regex_match(email, pattern);
}
int main() {
    std::string emails[] = {
        "user@example.com",
        "invalid.email",
        "test@sub.domain.com",
        "@example.com"
    };
    
    for (const auto& email : emails) {
        std::cout << email << ": " 
                  << (isValidEmail(email) ? "유효" : "무효") 
                  << std::endl;
    }
    
    return 0;
}

예제 2: URL 파싱

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
struct URL {
    std::string protocol;
    std::string domain;
    std::string path;
};
URL parseURL(const std::string& url) {
    std::regex pattern{R"(^(https?)://([^/]+)(/.*)?$)"};
    std::smatch matches;
    
    if (std::regex_match(url, matches, pattern)) {
        return {
            matches[1].str(),  // protocol
            matches[2].str(),  // domain
            matches[3].str()   // path
        };
    }
    
    return {};
}
int main() {
    std::string url = "https://example.com/path/to/page";
    URL parsed = parseURL(url);
    
    std::cout << "프로토콜: " << parsed.protocol << std::endl;
    std::cout << "도메인: " << parsed.domain << std::endl;
    std::cout << "경로: " << parsed.path << std::endl;
    
    return 0;
}

예제 3: 로그 파싱

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
#include <string>
#include <vector>
struct LogEntry {
    std::string timestamp;
    std::string level;
    std::string message;
};
std::vector<LogEntry> parseLog(const std::string& log) {
    std::vector<LogEntry> entries;
    
    // 로그 패턴: [2026-03-29 14:30:25] [INFO] Message
    std::regex pattern{R"(\[([^\]]+)\] \[(\w+)\] (.+))"};
    
    std::istringstream stream(log);
    std::string line;
    
    while (std::getline(stream, line)) {
        std::smatch matches;
        if (std::regex_match(line, matches, pattern)) {
            entries.push_back({
                matches[1].str(),  // timestamp
                matches[2].str(),  // level
                matches[3].str()   // message
            });
        }
    }
    
    return entries;
}
int main() {
    std::string log = 
        "[2026-03-29 14:30:25] [INFO] Server started\n"
        "[2026-03-29 14:30:26] [ERROR] Connection failed\n"
        "[2026-03-29 14:30:27] [DEBUG] Retrying...";
    
    auto entries = parseLog(log);
    
    for (const auto& entry : entries) {
        std::cout << entry.timestamp << " [" << entry.level << "] " 
                  << entry.message << std::endl;
    }
    
    return 0;
}

6. 자주 발생하는 문제

문제 1: 이스케이프

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
// ❌ 이스케이프 지옥
std::regex pattern1{"\\d+\\.\\d+"};
// ✅ Raw 문자열 리터럴
std::regex pattern2{R"(\d+\.\d+)"};
// 복잡한 패턴도 간결
std::regex email{R"(^[\w\.-]+@[\w\.-]+\.\w+$)"};

문제 2: 성능

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
#include <chrono>
#include <vector>
int main() {
    std::vector<std::string> texts = {"test1", "test2", "test3"};
    
    // ❌ 매번 regex 생성 (느림)
    auto start1 = std::chrono::high_resolution_clock::now();
    for (const auto& text : texts) {
        std::regex pattern{R"(\d+)"};  // 매번 생성!
        std::regex_search(text, pattern);
    }
    auto end1 = std::chrono::high_resolution_clock::now();
    
    // ✅ regex 재사용 (빠름)
    auto start2 = std::chrono::high_resolution_clock::now();
    std::regex pattern{R"(\d+)"};  // 한 번만 생성
    for (const auto& text : texts) {
        std::regex_search(text, pattern);
    }
    auto end2 = std::chrono::high_resolution_clock::now();
    
    auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(end1 - start1).count();
    auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(end2 - start2).count();
    
    std::cout << "매번 생성: " << duration1 << " μs" << std::endl;
    std::cout << "재사용: " << duration2 << " μs" << std::endl;
    
    return 0;
}

해결책: std::regex 객체를 재사용하세요.

문제 3: 예외 처리

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
int main() {
    try {
        // ❌ 잘못된 정규식
        std::regex pattern{"[invalid"};  // std::regex_error
        
    } catch (const std::regex_error& e) {
        std::cerr << "정규식 에러: " << e.what() << std::endl;
        std::cerr << "에러 코드: " << e.code() << std::endl;
    }
    
    return 0;
}

문제 4: 플래그

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
int main() {
    std::string text = "Hello World";
    
    // 대소문자 구분 (기본)
    std::regex pattern1{"hello"};
    std::cout << std::regex_search(text, pattern1) << std::endl;  // 0 (false)
    
    // 대소문자 무시
    std::regex pattern2{"hello", std::regex::icase};
    std::cout << std::regex_search(text, pattern2) << std::endl;  // 1 (true)
    
    return 0;
}

7. 정규식 문법 (ECMAScript)

문자 클래스

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

\d  // 숫자 [0-9]
\D  // 비숫자
\w  // 단어 문자 [a-zA-Z0-9_]
\W  // 비단어 문자
\s  // 공백 [ \t\n\r\f\v]
\S  // 비공백
.   // 모든 문자 (개행 제외)

수량자

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 실행 예제
*   // 0개 이상
+   // 1개 이상
?   // 0 또는 1개
{n}   // 정확히 n개
{n,}  // n개 이상
{n,m} // n개 이상 m개 이하

앵커

다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

^   // 문자열 시작
$   // 문자열 끝
\b  // 단어 경계
\B  // 비단어 경계

그룹

다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

()    // 캡처 그룹
(?:)  // 비캡처 그룹
|     // OR
[]    // 문자 클래스

8. 실전 예제: 텍스트 처리 유틸리티

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <regex>
#include <iostream>
#include <string>
#include <vector>
class TextProcessor {
public:
    // 이메일 추출
    static std::vector<std::string> extractEmails(const std::string& text) {
        std::vector<std::string> emails;
        std::regex pattern{R"([\w\.-]+@[\w\.-]+\.\w+)"};
        
        auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
        auto end = std::sregex_iterator();
        
        for (auto it = begin; it != end; ++it) {
            emails.push_back(it->str());
        }
        
        return emails;
    }
    
    // 전화번호 추출 (한국 형식)
    static std::vector<std::string> extractPhones(const std::string& text) {
        std::vector<std::string> phones;
        std::regex pattern{R"(\d{2,3}-\d{3,4}-\d{4})"};
        
        auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
        auto end = std::sregex_iterator();
        
        for (auto it = begin; it != end; ++it) {
            phones.push_back(it->str());
        }
        
        return phones;
    }
    
    // HTML 태그 제거
    static std::string removeHTMLTags(const std::string& html) {
        std::regex pattern{R"(<[^>]*>)"};
        return std::regex_replace(html, pattern, "");
    }
    
    // 연속 공백 제거
    static std::string normalizeSpaces(const std::string& text) {
        std::regex pattern{R"(\s+)"};
        return std::regex_replace(text, pattern, " ");
    }
    
    // URL 검증
    static bool isValidURL(const std::string& url) {
        std::regex pattern{R"(^https?://[\w\.-]+\.\w{2,}(/.*)?$)"};
        return std::regex_match(url, pattern);
    }
};
int main() {
    std::string text = "Contact: user@example.com or call 010-1234-5678";
    
    // 이메일 추출
    auto emails = TextProcessor::extractEmails(text);
    std::cout << "이메일:" << std::endl;
    for (const auto& email : emails) {
        std::cout << "  " << email << std::endl;
    }
    
    // 전화번호 추출
    auto phones = TextProcessor::extractPhones(text);
    std::cout << "전화번호:" << std::endl;
    for (const auto& phone : phones) {
        std::cout << "  " << phone << std::endl;
    }
    
    // HTML 태그 제거
    std::string html = "<p>Hello <b>World</b></p>";
    std::string plain = TextProcessor::removeHTMLTags(html);
    std::cout << "태그 제거: " << plain << std::endl;
    
    // 공백 정규화
    std::string messy = "Hello    World   !";
    std::string clean = TextProcessor::normalizeSpaces(messy);
    std::cout << "공백 정규화: " << clean << std::endl;
    
    // URL 검증
    std::cout << "URL 검증: " 
              << (TextProcessor::isValidURL("https://example.com") ? "유효" : "무효") 
              << std::endl;
    
    return 0;
}

정리

핵심 요약

  1. regex: C++11 정규 표현식 라이브러리
  2. regex_match: 전체 문자열 매치
  3. regex_search: 부분 문자열 검색
  4. regex_replace: 패턴 치환
  5. 캡처 그룹: () 로 부분 추출
  6. 반복자: 모든 매치 찾기

Regex 함수 비교

함수용도반환 타입사용 시기
regex_match전체 매치bool검증 (이메일, URL)
regex_search부분 매치bool검색 (숫자 찾기)
regex_replace치환string변환 (마스킹, 정리)
sregex_iterator모든 매치반복자추출 (모든 이메일)

실전 팁

성능:

  • std::regex 객체 재사용 (생성 비용 큼)
  • 간단한 패턴은 std::string 함수 사용
  • 대량 처리는 전용 라이브러리 고려 (RE2, PCRE) 가독성:
  • Raw 문자열 리터럴 사용 (R"(...)")
  • 복잡한 패턴은 주석으로 설명
  • 캡처 그룹에 의미있는 변수명 안전성:
  • 예외 처리 (std::regex_error)
  • 입력 검증 (길이 제한)
  • 타임아웃 고려 (복잡한 패턴)

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3