Vector中的动态内存分配

时间:2017-06-13 07:15:26

标签: c++ vector stl dynamic-memory-allocation

我对向量中的内存分配有疑问(STL - C ++)。据我所知,每当矢量大小等于其容量时,它的容量就会动态增加一倍。如果是这种情况,分配怎么连续?它是如何仍然允许使用[]访问运算符进行O(1)访问,就像数组一样?谁能解释这种行为? (列表也有动态内存分配,但是我们无法使用[]访问运算符访问它的元素,如何使用向量?)

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

  @Autowired
  @Qualifier("hibernateTemplateSecondary")
  private HibernateTemplate hibernateTemplateSecondary;

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof MyComponent) {
      ((MyComponent) bean).setHibernateTemplate(hibernateTemplateSecondary);
    }
    return bean;
  }
}
  

输出:

     

1 1
  2 2
  3 4
  4 4
  5 8
  6 8
  7 8
  8 8
  9 16
  10 16

3 个答案:

答案 0 :(得分:4)

  

据我所知,每当矢量大小等于其容量时,其容量会动态增加一倍。

需要在您的情况下加倍,它的实现已定义。因此,如果您使用其他编译器,它可能会有所不同。

  

如果是这种情况,分配怎么连续?

如果向量无法分配更多连续内存,则向量必须将其数据移动到符合其大小要求的新连续内存块。旧块将被标记为空闲,以便其他人可以使用它。

  

如何像数组一样使用[]访问运算符进行O(1)访问?

由于事先说过,[] operatorpointer + offset可以访问。对数据的访问将是O(1)。

  

列表也有动态内存分配,但是我们无法使用[]访问运算符访问其元素,如何使用向量?

列表(std::list for example)与std::vector完全不同。在C ++ std :: list的情况下,它保存带有数据的节点,指向下一个节点的指针和指向前一个节点的指针(双链表)。因此,您必须遍历列表才能获得所需的特定节点。 矢量的工作方式如上所述。

答案 1 :(得分:1)

vector 必须将对象存储在一个连续的内存区域中。因此,当它需要增加其容量时,它必须分配一个新的(更大的)内存区域(或扩展它已有的内存区域,如果可能的话),并从&#34复制或移动对象;老,小"区域到新分配的区域。

使用带有副作用(ideone link)的复制/移动构造函数的类可以明白这一点:

#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;

#define V(p) static_cast<void const*>(p)

struct Thing {
    Thing() {}
    Thing(Thing const & t) {
        cout << "Copy " << V(&t) << " to " << V(this) << endl;
    }
    Thing(Thing && t) /* noexcept */ {
        cout << "Move " << V(&t) << " to " << V(this) << endl;
    }
};

int main() {
    vector<Thing> things;
    for (int i = 0; i < 10; ++i) {
        cout << "Have " << things.size() << " (capacity " << things.capacity()
            << "), adding another:\n";
        things.emplace_back();
    }
}

这将导致输出类似于

[..]
Have 2 (capacity 2), adding another:
Move 0x2b652d9ccc50 to 0x2b652d9ccc30
Move 0x2b652d9ccc51 to 0x2b652d9ccc31
Have 3 (capacity 4), adding another:
Have 4 (capacity 4), adding another:
Move 0x2b652d9ccc30 to 0x2b652d9ccc50
Move 0x2b652d9ccc31 to 0x2b652d9ccc51
Move 0x2b652d9ccc32 to 0x2b652d9ccc52
Move 0x2b652d9ccc33 to 0x2b652d9ccc53
[..]

这表明,在向量中添加第三个对象时,它已经包含的两个对象从一个连续区域移动(查看1(sizeof(Thing))个增加的地址到另一个连续区域。添加第五个对象时,您可以看到第三个对象确实直接放在第二个对象之后。

何时移动以及何时复制?移动构造函数被视为when it is markednoexcept(或者编译器可以推断出它)。否则,如果允许抛出,则向量可能最终处于其对象的某些部分位于新内存区域中的状态,但其余部分仍然在旧内存区域中。

答案 2 :(得分:0)

应该在两个不同的层面考虑这个问题。

从标准的角度来看,需要提供连续存储以允许程序员使用其第一个元素的地址作为数组第一个元素的地址。当你通过重新分配添加新元素仍然保留以前的元素时,需要让它的容量增长 - 但它们的地址可能会改变。

从实现的角度来看,它可以尝试扩展分配的内存,如果不能,则分配一个全新的内存块,并在新分配的内存区域中移动或复制构造现有元素。标准未指定大小增加,而是留给实现。但你是对的,每次加倍分配大小是常见用法。