c++ - g++ 和 boost 中 regex_replace() 替换字符串中 '\' 的不同处理

标签 c++ c++11 gcc boost

将 gcc 从 4.8.5 版升级到 5.3.1 版后,我想我 可以摆脱 boost 的正则表达式实现(boost 版本 1.54.0)和 使用 gcc 提供的那个(在 4.9 版本之前它不能与 gcc 一起工作 AFAIK)。然而,这是一个问题,因为那两个 实现行为不同:

#include <regex>
#include <boost/regex.hpp>
#include <iostream>
#include <string>

int main() {
    std::string s="\\needs_another_backslash";
    std::string reg("^(\\\\)(needs)(.+)");
    std::string rep("\\\\got$3");
    std::regex sr(reg);
    boost::regex br(reg);
    std::cout<<"string before replacement:\n"<<s<<std::endl<<
        "regular expression:\n"<<reg<<std::endl<<
        "replacement string:\n"<<rep<<std::endl<<
        "std::regex_replace:\n"<<std::regex_replace(s,sr,rep)<<std::endl<<
        "boost::regex_replace:\n"<<boost::regex_replace(s,br,rep)<<std::endl;
    return 0;
}

这给出了以下输出:

替换前的字符串: \needs_another_backslash 正则表达式: ^(\\)(需要)(.+) 替换字符串: \\得到$3 std::regex_replace: \\got_another_backslash boost::regex_replace: \got_another_backslash

似乎 boost 对替换字符串中的 '\' 进行了特殊处理 而 gcc 没有。由于 std::regex_replace 的替换字符串中反向引用的神奇字符是“$”(它也在 boost 中作为示例证明),我倾向于认为 gcc 是正确的。然而,在许多其他程序中(例如 vim),它是 '\'。因此,boost 可能有特殊处理 '\' 的意义。那么谁是对的?

最佳答案

首先,std 示例实际上不是 gcc 的问题,而是 C++ 标准的问题,gcc(在本例中)符合该标准。标准在 28.5.2 中声明:

When a regular expression match is to be replaced by a new string, the new string shall be constructed using the rules used by the ECMAScript replace function in ECMA-262, part 15.5.4.11 String.prototype.replace. In addition, during search and replace operations all non-overlapping occurrences of the regular expression shall be located and replaced, and sections of the input that did not match the expression shall be copied unchanged to the output string.

ECMA状态:

Otherwise, let newstring denote the result of converting replaceValue to a String. The result is a String value derived from the original input String by replacing each matched substring with a String derived from newstring by replacing characters in newstring by replacement text as specified in Table 22. These $ replacements are done left-to-right, and, once such a replacement is performed, the new replacement text is not subject to further replacements. For example, "$1,$2".replace(/(\$(\d))/g, "$$1-$1$2") returns "$1-$11,$1-$22". A $ in newstring that does not match any of the forms below is left as is.

(如果部分:replaceValue 是一个函数。)

没有提到要替换转义序列。尝试使用 Firefox:

var test = "\\needs_another_backslash";
test = test.replace(/^(\\)(needs)(.+)/, "\\\\got$3");
alert(test);

结果:\\got_another_backslash

boost documentation状态:

Effects: If fmt is either a null-terminated string, or a container of char_type's, then copies the character sequence [fmt.begin(), fmt.end()) to OutputIterator out. For each format specifier or escape sequence in fmt, replace that sequence with either the character(s) it represents, or the sequence of characters within *this to which it refers. The bitmasks specified in flags determines what format specifiers or escape sequences are recognized, by default this is the format used by ECMA-262, ECMAScript Language Specification, Chapter 15 part 5.4.11 String.prototype.replace.

此外,它声明 match_type_flags :

Specifies that when a regular expression match is to be replaced by a new string, that the new string is constructed using the rules used by the ECMAScript replace function in ECMA-262, ECMAScript Language Specification, Chapter 15 part 5.4.11 String.prototype.replace. (FWD.1).

This is functionally identical to the Perl format string rules.

[...]

在 Linux 上尝试使用 perl 5.18.2:

my $test = "\\needs_another_backslash";
$test =~ s/^(\\)(needs)(.+)/\\\\got$3/;
print "$test\n";

结果为 \\got_another_backslash

使用std::string reg("^(\\\\)(needs)(.+)");,作为一个字符串文字被传递,reg保存一个字符串 ^(\\)(needs)(.+)(不是文字,所以省略了引号!),并使用 std::string rep("\\\\got$3"); ,rep 持有 \\got$3

但在解释上显然存在差异。假设我们对 std 和 boost 都有一个相同的 ECMAScript 引擎。

然后,std 和 boost 始终如一地做的是将 reg 字符串编译为正则表达式:

sprintf(b, "/%s/", reg);
sr /* br, respectively */ = ECMAScriptEngine::compileFromSource(b);

我认为通过创建 std/boost::regex 类的实例可以很好地反射(reflect)这一点。

然而,不同之处在于:std 将 ssrrep 传递给 ECMAScript 引擎,以便它直接调用 s.(String.prototype.replace)(sr, rep);(当然现实中 s 没有这样的函数——假设我们可以这样做)。

boost 也可以编译 rep 字符串(旁注:我还没有安装 boost,所以我自己没有验证这种行为...):

sprintf(b, "'%s'", rep); // note: '', not //!
ecma_rep = ECMAScriptEngine::compileFromSource(b);

然后让引擎调用s.(String.prototype.replace)(sr, ecma_rep);

有趣的是,boost 不编译源字符串 s,它再次与 std 一致......

不过,我认为最终,标准实现更接近于我们实际想要做的事情:

s.replace(regex, string);
s.replace(/reg/, rep);
(std::string).replace(std::regex(std::string), std::string);
std::regex_replace(s, std::regex(reg), rep);

对比

s.replace(regex, string);
s.replace(/reg/, "rep");
(std::string).replace(boost::regex(std::string), boost::???(std::string));
boost::regex_replace(s, boost::regex(reg), rep); // not boost::???(rep)!

不确定这是否足以说明一个是对的,另一个是错的(这意味着错误的是错误的!)。可能我们甚至不得不保留第三种选择:两种方法都是有效的(所以都是对的,没有一个是错的),不幸的是,它们是不兼容的......

关于c++ - g++ 和 boost 中 regex_replace() 替换字符串中 '\' 的不同处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37829849/

相关文章:

c++ - 将字符串对象的 vector 输出到文件

c++ - x64 构建 : error MSB8013: This project doesn't contain the Configuration and Platform combination of Debug|Win32

c++ - 从基类构造函数调用派生类的方法

c - 如何在没有任何优化的情况下编译C程序

c - LD_INCLUDE_PATH 实际上做了什么吗?

c++ - clang 和 gcc 中是否有 Visual C++ __declspec (属性声明属性)的替代方案?

c++ - 为什么这个 SFINAE 在 gcc 中会报错?

c++ - MSVS2010中 "%"宏和 "$"宏的区别

c++ - 当 auto 遇到多态和虚函数时,正确的行为是什么?

c++ - 向类中添加新成员变量会影响二进制兼容性吗?