C++ std::vector::swap实际上做什么?

C++ std::vector::swap实际上做什么?,c++,vector,swap,C++,Vector,Swap,引发这一问题的是以下代码: std::vector<int> x(500); std::vector<int> y; std::swap(x,y); vector内部存储(至少)指向元素实际存储的指针、大小和容量。†std::swap仅交换指针、大小和容量(以及辅助数据,如有);由于x中的指针变为y中的指针,反之亦然,且未分配任何新内存,因此不会对内存或元素进行加倍 vector的迭代器通常是围绕指向底层分配内存的指针的轻量级包装器(这就是容量更改通常使迭代器无效的原

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

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

std::swap(x,y);

vector
内部存储(至少)指向元素实际存储的指针、大小和容量。†
std::swap
仅交换指针、大小和容量(以及辅助数据,如有);由于
x
中的指针变为
y
中的指针,反之亦然,且未分配任何新内存,因此不会对内存或元素进行加倍

vector
的迭代器通常是围绕指向底层分配内存的指针的轻量级包装器(这就是容量更改通常使迭代器无效的原因);您使用的
sort
示例是合法的,并且sorts
y

如果您想在不交换存储的情况下交换元素本身(这是一个更为昂贵的操作,但它会为
x
引用
x
留下先前存在的迭代器),您可以这样做,但这是一个相对不常见的用例。其内存使用情况取决于底层对象的
swap
实现;默认情况下,它通常会涉及一个临时变量,但一次只能交换一个对象,而不是整个向量



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

vector
内部存储(至少)指向元素实际存储的指针,大小和容量。†
std::swap
仅交换指针、大小和容量(以及辅助数据,如果有的话);由于
x
中的指针变为
y
中的指针,反之亦然,且未分配任何新内存,因此不会对内存或元素进行加倍

vector
的迭代器通常是围绕指向底层分配内存的指针的轻量级包装器(这就是容量更改通常使迭代器无效的原因);您使用的
sort
示例是合法的,并且sorts
y

如果您想在不交换存储的情况下交换元素本身(这是一个更为昂贵的操作,但它会为
x
引用
x
留下先前存在的迭代器),您可以这样做,但这是一个相对不常见的用例。其内存使用情况取决于底层对象的
swap
实现;默认情况下,它通常会涉及一个临时变量,但一次只能交换一个对象,而不是整个向量



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

我将编写一个玩具向量

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; }
};
int&运算符[](std::size_t i){返回缓冲区[i];}
空位储备(国际容量){
if(容量缓冲区大小){
保留((标准::最大值)((缓冲区大小*3/2),缓冲区大小+1));
缓冲区[有效计数]=标准::移动(x);
++有效计数;
}
//真正的dtor更复杂。
~toy_vector(){delete[]buffer;}
};
实际的向量存在异常安全问题,更关注对象生存期和分配器(我使用的是INT,所以不关心是否正确构造/销毁它们),并且可能存储3个指针而不是一个指针和2个大小。但是交换这3个指针就像交换指针和2个大小一样简单

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


C++标准没有明确地指出“强>任务< /强>这样的实现,但是它是基于这样的实现而建立的,它隐含地要求一个与此几乎相同的实现。(我相信有人会想出一个聪明的标准兼容向量,它看起来不是这样的;但我见过的每个标准库中的每个向量都是这样的。)

我将编写一个玩具向量

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; }
};
int&运算符[](std::size_t i){返回缓冲区[i];}
空位储备(国际容量){
if(容量缓冲区大小){
保留((标准::最大值)((缓冲区大小*3/2),缓冲区大小+1));
缓冲区[有效计数]=标准::移动(x);
++有效计数;
}
//真正的dtor更复杂。
~toy_vector(){delete[]buffer;}
};
实际的向量存在异常安全问题,更关注对象生命周期和分配器(我使用的是INT,所以不关心是否正确构造/销毁它们),可能存储3个指针,而不是一个指针和2个大小。但是交换这3个指针就像交换指针和2个大小一样简单

实向量中的迭代器往往不是原始指针(但它们可以是)。如上所述,当您执行
a.swap(b)
;非原始指针向量iter时,指向向量
a
的原始指针迭代器变成指向向量
b
的原始指针迭代器