【问题标题】:std::strings's capacity(), reserve() & resize() functionsstd::strings 的容量()、保留()和调整大小()函数
【发布时间】:2012-03-20 06:25:16
【问题描述】:

我只想使用 std::string 来创建一个动态缓冲区,而不是使用索引遍历它。 resize() 是唯一实际分配缓冲区的函数吗?

我尝试了 reserve() 但是当我尝试通过索引访问字符串时,它断言。此外,当字符串的默认容量似乎是 15 个字节(在我的情况下)但我仍然无法以 my_string[1] 访问它时。

那么字符串的容量不是实际的缓冲区吗?另外reserve() 也不分配实际的缓冲区?

string my_string;

// I want my string to have 20 bytes long buffer
my_string.reserve( 20 );

int i = 0;

for ( parsing_something_else_loop )
{
    char ch = <business_logic>;

    // store the character in 
    my_string[i++] = ch; // this crashes
}

如果我使用 resize() 而不是 reserve() 就可以了。字符串有容量但不能用[]真正访问它是怎么回事?这不就是要保留()大小以便您可以访问它吗?

插件 作为对答案的回应,我想问 stl 的人,当 resize() 完全相同并且还初始化字符串时,为什么有人会使用 reserve() ?我不得不说我不太欣赏这种情况下的性能论点。除了reserve() 所做的之外,resize() 所做的所有事情就是它只是初始化我们知道无论如何总是好的缓冲区。我们可以在岛上投票 reserve() 吗?

【问题讨论】:

  • 字节向量会更合适
  • std::vector 似乎也可以满足您的要求。使用 std::vector 的答案也是可以接受的答案吗?

标签: c++ string stl


【解决方案1】:

std::vector 代替 std::string 也可能是一种解决方案 - 如果没有针对它的要求。

vector<char> v; // empty vector
vector<char> v(10); // vector with space for 10 elements, here char's

你的例子:

vector<char> my_string(20);

int i=0;

for ( parsing_something_else_loop )
{
    char ch = <business_logic>;
    my_string[i++] = ch;
}

【讨论】:

  • 这并没有真正回答 OP 的实际问题,而只是将其转换为一个完全相同的问题。
  • 我在下面的评论中询问了 std::vector 是否可能是一个选项,看到 no yes 或 no 并提供了一个替代解决方案,即使标记为 is,作为替代 ("std::vector而不是 std::string 也可能是一个解决方案")。我没有看到你看到的伤害。
  • 我也没有看到任何伤害,只要 cmets 保留 cmets 并且答案保留答案。但是您的“答案”只是一个评论,虽然是一个很好的评论,但仍然是一个评论,而不是对他任何问题的回答。
【解决方案2】:

这不是要reserve() 大小以便您可以访问它吗?

不,这就是resize() 的意义所在。

reserve() 只提供足够的空间,以便将来导致大小增加的调用(例如调用push_back())更有效率。

从您的用例看来,您应该改用 .push_back()

my_string.reserve( 20 );

for ( parsing_something_else_loop )
{
    char ch = <business_logic>;
    my_string.push_back(ch);
}

为什么字符串有容量却不能用[]真正访问?

拨打.reserve() 就像炸毁山峰给你一些免费的土地。免费土地的数量是.capacity()。土地在那里,但这并不意味着你可以在那里生活。你必须建造房子才能搬进来。房子的数量是.size()(=.length())。

假设你正在建造一座城市,但是在建造了第 50 宫之后你发现土地不够用,所以你需要另找一个足够大的地方来容纳第 51 宫,然后将整个人口迁移到那里。这是非常低效的。如果您知道需要预先建造 1000 座房屋,那么您可以致电

my_string.reserve(1000);

获得足够的土地来建造 1000 座房屋,然后你打电话

my_string.push_back(ch);

ch 分配到这个位置来建造房子。容量是1000,但是size还是1。你可能不会说

my_string[16] = 'c';

因为 16 号房子还不存在。你可以打电话

my_string.resize(20);

一口气建好#0~#19的房子,这就是为什么

my_string[i++] = ch;

工作正常(只要 0 ≤ i ≤ 19)。

另见http://en.wikipedia.org/wiki/Dynamic_array


关于您的附加问题,

.resize()不能完全替代.reserve(),因为(1)你并不总是需要用完所有分配的空间,(2)默认构造+复制分配是一个两步过程,可能需要更多比直接构建时间(尤其是对于大型对象),即

#include <vector>
#include <unistd.h>

struct SlowObject
{
    SlowObject() { sleep(1); }
    SlowObject(const SlowObject& other) { sleep(1); }
    SlowObject& operator=(const SlowObject& other) { sleep(1); return *this; }
};

int main()
{
    std::vector<SlowObject> my_vector;

    my_vector.resize(3);
    for (int i = 0; i < 3; ++ i)
        my_vector[i] = SlowObject();

    return 0;
}

会浪费你至少 9 秒的时间来运行,而

int main()
{
    std::vector<SlowObject> my_vector;

    my_vector.reserve(3);
    for (int i = 0; i < 3; ++ i)
        my_vector.push_back(SlowObject());

    return 0;
}

仅浪费 6 秒。

std::string 在这里只复制std::vector 的界面。

【讨论】:

  • 这样想:字符串还是有大小的,reserve()没有改变;但是,您已经分配了一块连续的内存,因此该字符串可能是resize()'d 而不会导致整个字符串的复制。但是,它仍然会检查您的索引是否在字符串的(逻辑)大小范围内。当您提前了解字符串的大小时,保留是一种优化 - 就像 char[1000] 的 c 习惯用法 - 即使字符串可能只是“Hello, world”
  • 所以假设我从您的示例开始,使用 'my_string.resize(1000)',性能真正受到什么影响?我会认为它更好,因为它减少了代码中的一行(稍后的 resize() ),所以我需要管理的代码更少。
  • @KennyTM 您的附加示例是关于向量的,但我只想限制为字符串类。无论如何,OOP 都是关于对象的,对象初始化是它的基本部分。我们是否要忽略 OOP 原则以实现稍微提高效率?
  • @zadane:你可以有std::basic_string&lt;SlowObject&gt;。 OOP是关于封装、继承和多态,但是对象初始化,std::string与OOP无关。
  • @zadane:此外,由于向后兼容性,几乎不可能从 C++ 中删除任何功能,即使现在不合逻辑。 (三元组、vector&lt;bool&gt;strstream 等)
【解决方案3】:

仅仅因为reserve 分配了额外的空间并不意味着你可以访问它是合法的。

在您的示例中,要么使用resize,要么将其重写为:

string my_string;

// I want my string to have 20 bytes long buffer
my_string.reserve( 20 );

int i = 0;

for ( parsing_something_else_loop )
{
    char ch = <business_logic>;

    // store the character in 
    my_string += ch;
}

【讨论】:

    【解决方案4】:

    reserve(n) 确实分配了足够的存储空间来至少容纳n 元素,但它实际上并没有用任何元素填充容器。字符串仍然是空的(大小为 0),但可以保证,在需要重新分配字符串的内部缓冲区之前,您可以添加(例如,通过 push_backinsert)至少 n 元素,而 @987654326 @ 确实调整了字符串的大小以包含 n 元素(并在必要时删除或添加新元素)。

    所以reserve 实际上只是一个优化工具,当你知道你正在向容器中添加一堆元素(例如在push_back 循环中)并且不希望它过于频繁地重新分配存储时,产生内存分配和复制成本。但它不会改变字符串的外部/客户端视图。它仍然为空(或保持其当前元素计数)。

    同样,capacity 返回字符串在需要重新分配其内部存储之前可以容纳的元素数,而size(对于字符串还有length)返回字符串中的实际元素数。

    【讨论】:

      【解决方案5】:

      不——reserve 的目的是防止重新分配。 resize 设置可用大小,reserve 不设置 - 它只是设置保留但尚未直接可用的空间量。

      这是一个例子——我们将创建一个 1000 个字符的随机字符串:

      static const int size = 1000;
      std::string x;
      x.reserve(size);
      for (int i=0; i<size; i++)
         x.push_back((char)rand());
      

      reserve主要是一个优化工具——大多数使用reserve 的代码也应该可以工作(只是,可能会慢一点),而无需调用reserve。一个例外是reserve 可以确保迭代器保持有效,而如果没有调用reserve 就不会。

      【讨论】:

        【解决方案6】:

        capacity 实际缓冲区的长度,但该缓冲区对于字符串是私有的;换句话说,它不是你的访问权限。标准库的std::string可能分配比存储字符串实际字符所需的更多内存。容量是分配的总长度。但是,访问s.begin()s.end()之外的字符仍然是非法的。

        如果您希望调整字符串大小以避免不必要的重新分配,请致电reserve。例如,如果您计划在一个循环中连接 10 个 20 个字符的字符串,则为您的字符串保留 201 个字符(一个额外的字符用于零终止符)可能是有意义的,而不是将其从默认大小扩展几次.

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-05-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多