C++正则表达式传入字符串字面量时23字符匹配失效问题
std::smatch的引用残留 我们一步步拆解你遇到的异常行为:
1. 第一个Scanner版本的问题:临时字符串导致迭代器悬空
你的初始Scanner结构体只存储了字符串的迭代器,不持有字符串的所有权:
struct Scanner { std::string::const_iterator read_head; std::string::const_iterator eof; Scanner(std::string const& program) { read_head = program.cbegin(); eof = program.cend(); } };
当你用临时字符串构造s3和s4时:
Scanner s3("aaaaaaaaaaaaaaaaaaaaaa"); // 临时字符串构造 Scanner s4("aaaaaaaaaaaaaaaaaaaaaaa");
临时字符串在Scanner构造完成后就会被销毁,此时s3和s4中的read_head、eof就变成了悬空迭代器(指向已经被释放的内存)。后续调用scan时使用这些悬空迭代器属于C++中的未定义行为——你看到s4返回false只是随机出现的异常结果。
2. 第二个Scanner版本的问题:静态std::smatch持有无效引用
修改后的Scanner虽然持有了字符串所有权,但scan函数中的static std::smatch match是静态变量,它的生命周期贯穿整个程序:
bool scan(Scanner const& scanner) { static std::smatch match; // 静态变量,仅初始化一次 std::regex regex = std::regex("a+"); return std::regex_search(scanner.read_head, scanner.eof, match, regex, match_continuous); }
std::smatch内部会保存指向输入字符串序列的迭代器或引用。第一次调用scan(s1)时,match绑定了s1.program的内存;第二次调用scan(s2)时,std::regex_search会尝试复用这个match对象,但此时match可能还残留着指向s1.program的无效引用。如果s2.program的内存位置刚好和s1.program重叠(比如内存分配器复用了之前的内存块),就会导致匹配逻辑混乱,出现返回false的异常。
3. 值传递参数解决问题的原因
当你把scan的参数改成值传递:
bool scan(Scanner scanner) { ... }
每次调用scan时都会创建一个Scanner的拷贝,这个拷贝的program字符串在scan函数调用期间完全有效,迭代器不会悬空。同时,虽然match还是静态的,但每次匹配时都会用当前有效字符串的迭代器覆盖match内部的状态,因此所有匹配都能正常完成,返回true。
彻底修复方案
推荐从两个方向入手解决:
- 移除静态
std::smatch:把match改成scan函数的局部变量,避免残留之前的无效状态:
同时保留bool scan(Scanner const& scanner) { using std::regex_constants::match_continuous; std::smatch match; // 局部变量,每次调用重新初始化 std::regex regex = std::regex("a+"); return std::regex_search(scanner.read_head, scanner.eof, match, regex, match_continuous); }Scanner持有字符串所有权的版本,确保迭代器始终有效。 - 避免临时字符串构造Scanner:如果坚持用初始的
Scanner版本,要确保构造Scanner的字符串在Scanner生命周期内始终存在(比如用命名变量而非临时对象)。
内容的提问来源于stack exchange,提问作者Michael Francis

