【问题标题】:different treatment of '\' in regex_replace() replacement string in g++ and boostg ++和boost中regex_replace()替换字符串中'\'的不同处理
【发布时间】:2016-10-16 05:43:58
【问题描述】:

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

#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;
}

这给出了以下输出:

string before replacement: \needs_another_backslash regular expression: ^(\\)(needs)(.+) replacement string: \\got$3 std::regex_replace: \\got_another_backslash boost::regex_replace: \got_another_backslash

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

【问题讨论】:

  • 我不明白std::string rep("\\\\got$3"); 如何变成\\\\got$3 输出(在第一部分中只显示变量的值)- 我希望\\got$3 输出(就像reg 被处理)。
  • @Ped7g 是的,你是对的。对不起。我不小心复制了程序的输出,它以正确的方式得到了它。我纠正它。谢谢
  • 要在boost 中获得与std 实现相同的行为,您可以使用标志format_literal 例如boost::regex_replace(text, rgx, fmt, match_flag_type::format_literal)

标签: c++ c++11 gcc boost


【解决方案1】:

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

当一个正则表达式匹配被一个新的字符串替换时,新的 字符串应使用 ECMAScript 替换使用的规则构造 ECMA-262 中的函数,第 15.5.4.11 部分 String.prototype.replace。此外, 在搜索和替换操作期间,所有不重叠的 正则表达式应被定位和替换,输入的部分 与表达式不匹配的应原样复制到输出 字符串。

ECMA 表示:

否则,让 newstring 表示将 replaceValue 转换为 String 的结果。结果是从原始输入字符串派生的字符串值,方法是将每个匹配的子字符串替换为从 newstring 派生的字符串,方法是用表 22 中指定的替换文本替换 newstring 中的字符。这些 $ 替换是从左到右完成的,并且,一旦执行了这样的替换,新的替换文本就不会被进一步替换。例如, "$1,$2".replace(/(\$(\d))/g, "$$1-$1$2") 返回 "$1-$11,$1-$22"。 newstring 中与以下任何形式都不匹配的 $ 保持原样。

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

没有提到转义序列被替换。用火狐试了一下:

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

结果:\\got_another_backslash。

提升documentation 状态:

效果:如果 fmt 是一个以 null 结尾的字符串,或者是一个 char_type 的容器,则将字符序列 [fmt.begin(), fmt.end()) 复制到 OutputIterator 输出。对于 fmt 中的每个格式说明符或转义序列,将该序列替换为它所代表的字符,或者它所引用的 *this 中的字符序列。 flags 中指定的位掩码确定识别哪些格式说明符或转义序列,默认情况下这是 ECMA-262,ECMAScript 语言规范,第 15 章第 5.4.11 部分 String.prototype.replace 使用的格式。

另外,它声明match_type_flags:

指定当正则表达式匹配被新字符串替换时,新字符串是使用 ECMA-262,ECMAScript 语言规范,第 15 章第 5.4.11 部分字符串中的 ECMAScript 替换函数使用的规则构造的.prototype.replace。 (FWD.1)。

这在功能上与 Perl 格式字符串规则相同。

[...]

在 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 类的实例可以很好地反映这一点。

然而不同的是:std 将s、sr 和rep 传递给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)!

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

【讨论】:

  • 我同意 std::regex_replace 是标准的,而不是 gcc。但是我故意这样说是为了强调这是标准的gcc版本。 (其他编译器呢?)这就是问题的重点:哪个版本符合标准?由于替换字符串中似乎没有提到反斜杠的任何特殊性,我更倾向于认为 gcc 是正确的。在我看来,“哪个是正确的版本?”必须有一个答案。怎么会有两个正确的实现产生不同的结果?毕竟,这不是未定义的行为,;-)
  • @diffset 等一下 - 提升不是标准!如果您将 gcc 与 clang 或 msvc 进行比较,并且在使用标准正则表达式库时存在差异 -- 那么 您可能会争辩说一个是对的,另一个不是。 boost 必须被视为一个竞争库(尽管它对标准有很大的影响)而不是标准的实现,并且在这方面完全允许这样做或以不同的方式进行。
  • @diffset 一个例子:假设你实现了你自己的向量版本——在你不需要它时放弃对连续内存的要求(以及这方面的一些其他东西),允许实现更多高性能的重新分配算法(将新的垃圾添加到现有的垃圾中,而不是重新分配 everything)。当然,这不符合标准——但它也不是标准的实现。因此,只要您不假装是标准,一切都很好(确实,boost 的 regex_replace 共享名称,但在不同的命名空间中:boost 而不是std)。
  • 我同意。升压不是标准的。但另一方面,无论是标准文档还是 boost 文档,我(你显然都没有)在 rep 字符串中找不到对 '\' 特殊字符的任何引用。特殊对待它的唯一原因是它是否具有特殊含义。它没有。在那些它是 backref 字符的语言中,它必须被转义,例如在 MySQL 中:select regexp_replace("\\needs_another_backslash", "^(\\\\)(needs)(.+)", "\\\\got\\3");。但是为什么在这里。 perl 能解释吗?我不熟悉那种语言。
  • @diffset Perl 在这方面是不同的,因为在这种语言中,您将替换打包到一个正则表达式中:s/pattern/replacement/。如果你现在把它写成文字,当然模式和替换都需要转义。这是我对类比的想法。但是忘记perl,我有一个更好的主意来解释......
猜你喜欢
  • 2013-04-27
  • 2021-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
  • 2011-02-15
  • 1970-01-01
相关资源
最近更新 更多