std::cin >> std::string 是如何实现的?

3

特别是,代码如何检查字符的内存是否需要重新分配?或者用户输入了多少个字符? 如果我想将C字符串的值分配给我的字符串类实现,我可能会这样做

   String& operator=(String& to, const char *from)
   {
      if((strlen(from) + 1) > to.size) {
        if (to.str != NULL) {
          delete[] to.str;
          to.str = NULL;
        }
        to.size = strlen(from) + 1;
        to.str = new char[to.size];
      }
      strcpy(to.str, from);
      return to;
    }

很容易。但是std::string的运算符>>确实让我感到好奇。


你的代码不是异常安全的:如果分配失败,to.str 指向过时的内存。通常情况下,最好使用构造函数来实现赋值操作:T& T::operator=(S from) { T(from).swap(*this); return *this; }(但无法处理抛出移动赋值的情况)。 - Dietmar Kühl
@DietmarKühl 如果分配失败,"new" 不会抛出异常吗? - Anhil
@Anhil: 没错。如果new抛出异常,你就得到了一个具有错误 to.str 的对象。(你调用了delete,但从未为其赋值!)Dietmar的建议是将=放在其他构造函数的术语中(参见复制并交换惯用法),这是一个好建议,并避免了异常留下损坏对象的麻烦。 - Thanatos
@Anhil:是的,new char[...] 可能会抛出异常,但您无法恢复 to.str 的值(对于强烈的异常安全赋值,没有任何改变),并且您没有设置 to.str,至少要设置一个避免双重删除的值(也就是说,您的代码甚至没有提供基本的异常保证)。这就是为什么您的代码不具备异常安全性的全部原因。 - Dietmar Kühl
@Thanatos:哦,对了。谢谢。我应该学习一下这个复制和交换惯用语,以前从未见过。 - Anhil
显示剩余3条评论
2个回答

4

基本上,实现看起来像这样(忽略流和字符串都是模板的事实):

std::istream& operator>> (std::istream& in, std::string& value) {
    std::istream::sentry cerberos(in);
    if (cerberos) {
        value.erase();
        std::istreambuf_iterator<char> it(in), end;
        if (it != end) {
            std::ctype<char> const& ctype(std::use_facet<std::ctype<char> >(in.getloc()));
            std::back_insert_iterator<std::string> to(value);
            std::streamsize n(0), width(in.width()? in.width(): std::string::max_size());
            for (; it != end && n != width && !ctype.is(std::ctype_base::space, *it); ++it, ++to) {
                *to = *it;
            }
        }
    }
    else {
        in.setstate(std::ios_base::failbit);
    }
    return in;
}

一个合理的实现可能会使用一种算法,该算法将以段为单位处理流缓冲区的内容,例如,避免重复的检查和对 is() 的调用(尽管对于 std::ctype<char> 实际上只是将掩码应用于数组的元素)。无论如何,输入运算符都不会费力地分配内存:一个典型的情况是“不是我的工作”。


哇!那段代码看起来真的很棒!简单来说,它循环遍历输入缓冲区,并将字符插入到字符串末尾,直到达到空格/制表符/换行符,是吗? - Anhil
@Anhil:是的。从概念上讲,这就是它的工作原理,尽管实际的实现可能会以某种形式绕过流缓冲区和字符串的实际接口... - Dietmar Kühl

0

我相信它必须使用某种智能内存分配管理。如果你熟悉C语言,你应该见过函数realloc。我的想法是STL中的大多数容器类在内部使用某种形式的realloc来为自己分配更多的内存。

回答你的问题,字符串类是从另一个类std::basic_string<char>typedef而来,它基本上是一个字符数组。因此,在内部它有保留的内存,可以根据用户的偏好或需求增长或缩小。就像我之前提到的那样,这种内存管理方式是最优和安全的,以便不会丢失信息。

如果我要实现std::cin >> std::string,它将采用for循环的形式,遍历字符数组并为数组中的每个字符赋值。


实际上,std::string 并不是std::basic_string<char>派生的! std::stringstd::basic_string<char>是相同的类型。 std::stringstd::basic_string<char>typedef或别名。此外,请注意,确保字符串以空字符结尾是字符串类的工作:输入运算符仅是该类的客户端。 - Dietmar Kühl

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