【问题标题】:What does std::vector::swap actually do?std::vector::swap 实际上是做什么的?
【发布时间】:2020-12-10 15:47:47
【问题描述】:

引发这个问题的是以下代码:

std::vector<int> x(500);
std::vector<int> y;

std::swap(x,y);

我想知道交换两者是否需要两倍于 x 所需的内存量。

在cppreference 上我找到了std::vector::swap(这是最后一行有效调用的方法):

将容器的内容与其他容器的内容交换。不对单个元素调用任何移动、复制或交换操作。

所有迭代器和引用仍然有效。过去的迭代器无效。

现在我比以前更困惑了。当std::vector::swap 不移动、复制或交换元素时,它实际上做了什么? 迭代器如何保持有效?

这是否意味着这样的代码是有效的?

std::vector<int> x(500);
std::vector<int> y;
auto it = x.begin();
std::swap(x,y);
std::sort(it , y.end()); // iterators from different containers !!

【问题讨论】:

    标签: c++ vector swap


    【解决方案1】:

    vector 在内部存储(至少)一个指向元素实际存储的指针、大小和容量。†std::swap 只是交换指针、大小和容量(以及辅助数据(如果有);由于x 中的指针变为y 中的指针,反之亦然,因此不会造成内存翻倍或元素副本。

    vector 的迭代器通常是指向底层分配内存的指针的轻量级包装器(这就是容量更改通常会使迭代器失效的原因)so iterators produced for x before the swap seamlessly continue to refer to y after the swap;您对sort 的示例使用是合法的,并且对y 进行排序。

    如果您想要在不交换存储的情况下自行交换元素(这是一项成本更高的操作,但会为 x 留下现有的迭代器以引用 x),您可以 use std::swap_range ,但这是一个相对不常见的用例。其内存使用量取决于底层对象的swap 的实现;默认情况下,它通常会涉及一个临时的,但只针对一次交换的一个对象,而不是整个一个vector。


    † 根据 cmets,它可以等效地使用指向已用空间末尾和容量末尾的指针,但是这两种方法在逻辑上都是等效的,并且只是进行了微优化以支持略有不同的预期用例;存储所有指针优化迭代器的使用(一个合理的选择),而存储 size_type 优化 .size()/.capacity() 调用。

    【讨论】:

    • 查看标准,我很难找到任何关于迭代器保证有效性的方法。你能在上面加点什么吗?
    • 在我看到的大多数实现中 - 向量只有 3 个指针 - __ begin, __end, __end_of_capacity - 其中 size == __end - __begin;容量 == __end_of_capacity - __begin
    • @BoBTFish - 这应该是timsong-cpp.github.io/cppwp/n4861/…
    • @PiotrNycz:当然,这是一种有效的替代方法;关键是,在任何情况下,它都不会求助于制作相关vectors 之一的底层存储/元素的完整副本,实际上,它需要至少三个值(至少一个指针类型,和两个可能是指针类型或size_types,取决于实现选择)。无论哪种方式,它都是指针或size_types 的三个快速交换,无论vector 的大小或它存储的模板类型如何。
    • @StoryTeller-UnslanderMonica:我内联了那个链接;谢谢!
    【解决方案2】:

    我会写一个玩具矢量。

    struct toy_vector {
      int * buffer = 0;
      std::size_t valid_count = 0;
      std::size_t buffer_size = 0;
    
      int* begin() { return buffer; }
      int* end() { return buffer+valid_count; }
      std::size_t capacity() const { return buffer_size; }
      std::size_t size() const { return valid_count; }
    
      void swap( toy_vector& other ) {
        std::swap( buffer, other.buffer );
        std::swap( valid_count, other.valid_count );
        std::swap( buffer_size, other.buffer_size );
      }
    

    基本上就是这样。我将实现一些方法,以便您看到我们有足够的工具可以使用:

      int& operator[](std::size_t i) { return buffer[i]; }
    
      void reserve(int capacity) {
        if (capacity <= buffer_size)
          return;
        toy_vector tmp;
        tmp.buffer = new int[capacity];
        for (std::size_t i = 0; i < valid_count; ++i)
          tmp.buffer[i] = std::move(buffer[i]);
        tmp.valid_count = valid_count;
        tmp.buffer_size = capacity;
        swap( tmp );
      }
      void push_back(int x) {
        if (valid_count+1 > buffer_size) {
          reserve( (std::max)((buffer_size*3/2), buffer_size+1) );
        buffer[valid_count] = std::move(x);
        ++valid_count;
      }
      // real dtor is more complex.
      ~toy_vector() { delete[] buffer; }
    };
    

    实际的向量存在异常安全问题,更多关注对象生命周期和分配器(我使用的是整数,所以不要关心我是否正确构造/销毁它们),并且可能存储 3 个指针而不是一个指针和 2 个大小.但是交换这 3 个指针就像交换指针和 2 个大小一样简单。

    实向量中的迭代器往往不是原始指针(但它们可以是)。正如您在上面看到的,当您执行a.swap(b) 时,指向向量a 的原始指针迭代器变成指向向量b 的原始指针迭代器;非原始指针向量迭代器基本上是花哨的包装指针,并且必须遵循相同的语义。

    C++ 标准并没有明确强制一个看起来像这样的实现,但它是基于一个看起来像这样的实现,并且它隐含地需要一个几乎与这个相同的实现(I'我相信有人可以想出一个看起来不像这样的聪明的符合标准的向量;但是我见过的每个标准库中的每个向量都是这样的。)

    【讨论】:

      猜你喜欢
      • 2018-11-04
      • 1970-01-01
      • 1970-01-01
      • 2013-04-22
      • 2011-03-11
      • 2010-11-26
      • 2015-06-23
      • 2017-12-26
      • 2016-10-19
      相关资源
      最近更新 更多