basic_string的前导/尾随空格不敏感特性

3
我正在进行大量解析/处理工作,其中包括前导/尾随空格和不区分大小写。因此,我为std::basic_string创建了一个基本的字符特征(见下文),以省去一些工作。
该特征不起作用,问题在于basic_string的比较调用了特征比较函数,如果计算结果为0,则返回大小差异。在basic_string.h中,它说: ...如果比较的结果为非零值,则返回该值,否则较短的字符串比较小。 看起来他们明确不希望我这样做...
为什么要有这个额外的"较短的字符串"排序?是否有任何解决方法,还是我必须自己编写字符串处理代码?
#include <cstring>
#include <iostream>

namespace csi{
template<typename T>
struct char_traits : std::char_traits<T>
{
    static int compare(T const*s1, T const*s2, size_t n){
        size_t n1(n);
        while(n1>0&&std::isspace(*s1))
            ++s1, --n1;
        while(n1>0&&std::isspace(s1[n1-1]))
            --n1;
        size_t n2(n);
        while(n2>0&&std::isspace(*s2))
            ++s2, --n2;
        while(n2>0&&std::isspace(s2[n2-1]))
            --n2;
        return strncasecmp(static_cast<char const*>(s1),
                           static_cast<char const*>(s2),
                           std::min(n1,n2));
    }
};
using string = std::basic_string<char,char_traits<char>>;
}

int main()
{
    using namespace csi;
    string s1 = "hello";
    string s2 = " HElLo ";
    std::cout << std::boolalpha
              << "s1==s2" << " " << (s1==s2) << std::endl;
}

2
不是答案,但 std::isspace(x)(其中 xchar)必须写成 std::isspace((unsigned char)x)。否则,您会因为负字符代码而产生未定义的行为 - HolyBlackCat
@HolyBlackCat 当然可以,谢谢 :) - user8705939
2个回答

0

如果 trait 的比较返回 0,为什么还需要这个额外的“更短”的排序方式呢?

这就是basic_string::compare() 定义的方式。

那么,有没有解决方法,或者我必须自己写一个字符串?

看起来你的自定义 char_traits 必须实现以下内容:

  • length(),返回已修剪部分的长度,以及

  • move()copy(),用于复制修剪后的部分。


然而,有一个潜在的问题无法使用自定义特性解决。例如,basic_string 有像 basic_string(const CharT* s, size_type count, Allocator& alloc) 这样的构造函数,或者像 assigncompare 这样的方法重载,它们接受 C 字符串及其长度 - 在这些情况下,Traits::length() 不会被调用。如果有人使用其中任何一种方法,则字符串可能包含尾随空格或尝试访问源字符串末尾之外的字符。

为了解决这个问题,可以像这样做:

class TrimmedString
{
public:
    // expose only "safe" methods:
    void assign(const char* s) { m_str.assign(s); }

private:
    std::basic_sttring<char, CustomTraits> m_str;
};

或者这个(可能更简单):

class TrimmedString : private std::basic_string<char, CustomTraits>
{
public:
    using BaseClass = std::basic_string<char, CustomTraits>; // for readability

    // make "safe" method public
    using BaseClass::length;
    using BaseClass::size;
    // etc.

    // wrappers for methods with "unsafe" overloads
    void assign(const char* s) { BaseClass::assign(s); }
};

感谢您的回答。如果length()返回修剪后的大小,我该如何处理前导空格?例如。“ hello”-> [按照您的方法]->“hell”。我不认为有一种方法可以通过特性递增basic_string数据指针:/。即使我通过length()以某种方式完成它,我认为我会搞砸很多像basic_string的附加等等,对吗? - user8705939
@OZ17 这就是为什么您需要实现 copymove。这些方法只会复制中间部分,忽略源的开头和结尾部分。 - joe_chip
我在这方面有一点困难... 如果我改变特征中的 length()copy()move() 等,我将会有很多"不安全"的 basic_string 方法,如 begininsertappend。我基本上最终会写出 std::basic_string 类。 - user8705939
begin()是安全的,问题在于需要接受C字符串和长度的方法,例如assign的重载等。我示例中的想法是包装std::basic_string,使其不公开。这样就不需要实现它们-只需一个一行的包装器,调用basic_string中的方法即可。 - joe_chip

0
将具有多个可能表示的数据转换为“标准”或“正常”形式称为规范化。对于文本,通常意味着去除重音符号,大小写,修剪空格字符和/或格式字符。
如果每次比较都在幕后完成规范化,则它是脆弱的。例如,您如何测试已正确地对`s1`和`s2`执行了规范化?此外,它是不灵活的,例如,您无法显示其结果或将其缓存以进行下一次比较。因此,将其作为显式规范化步骤执行更加健壮和高效。
特质比较仅需要比较n个字符,因此当您比较“hellow”和“hello”时,它应该返回什么?它应该返回0。如果您以某种方式忽略了该n,那么您就处于有缺陷的情况,因为特质应该使用未以零结尾的std :: string_view工作。如果大小比较被删除,则“hellow”和“hello”将比较相等,而这可能不是您想要的。

谢谢!!!我花了一段时间重新思考这个问题... 我的特性能够正常工作的唯一合理方式是,如果我可以在修剪长度的特性内部执行这个“更短”的操作。但显然不可行,因为我无法覆盖 basic_string 的比较方法。另一方面,修改特性的 length() 方法会带来太多麻烦。我还是坚持显式修剪。再次感谢您!!! - user8705939

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接