std :: vector内存处理

时间:2016-04-12 09:00:09

标签: c++ vector

我试图谷歌搜索我的问题的答案,但我找不到任何有效的解释,因此我在这里发布我的问题。以下是我的示例代码和输出:

#include <iostream>
#include "vector"
using namespace std;

typedef struct Node{
    int data;
    Node(){
        data = 0;
        std::cout << "Node created. " << this <<'\n';
    }
    ~Node(){
        data = 0;
        std::cout << "Node destroyed. " << this <<'\n';
    }
} Node;

int main() {
    std::vector<Node> vec;
    for(int i = 0; i < 2 ; i++)
       vec.push_back( *(new Node));
    return 0;
}

输出:

Node created. 0x9e0da10
Node created. 0x9e0da30
Node destroyed. 0x9e0da20
Node destroyed. 0x9e0da40
Node destroyed. 0x9e0da44

为什么会有额外的破坏以及为什么创建的对象与被破坏的对象不同?

4 个答案:

答案 0 :(得分:39)

vec.push_back( *(new Node));是即时内存泄漏。

首先,您动态分配Node,然后将<{strong>} Node复制到向量中。复制操作是创建新对象的原因,这就是this不同的原因。

原始(动态分配的)Node永远不会被释放,但副本是在向量的析构函数运行时(即在函数的末尾)。

为什么三个析构函数调用而不是两个?这是由push_back时向量的自动重新分配引起的。它将其元素移动/复制到新的内存位置,从而破坏旧元素。

请注意,通常情况下,当您只需要n默认构造元素的向量时,您可以执行以下操作:

std::vector<Node> vec(2);

这会调用元素Node()vec[0]的{​​{1}}(默认构造函数),并且您不需要循环(或动态分配)。

答案 1 :(得分:18)

如果您要添加复制构造函数,您会找到答案:

Node created. 0x60200000df90
Node copied: 0x60200000df70  Source: 0x60200000df90
Node created. 0x60200000df50
Node copied: 0x60200000df34  Source: 0x60200000df50
Node copied: 0x60200000df30  Source: 0x60200000df70
Node destroyed. 0x60200000df70
Node destroyed. 0x60200000df34
Node destroyed. 0x60200000df30

因此,当您向向量添加第二个元素时,将没有足够的容量,向量必须调整存储大小并将所有旧元素(在这种情况下只有一个这样的元素)复制到新空间。这就是为什么要调用一个额外的拷贝构造函数的原因。

是的,在C ++中,所有标准容器都要求对象应该复制或移动构造,因此您不必在堆上创建它们(如在Java中)。代码越正确就越好:

#include <iostream>
#include "vector"


struct Node {
  int data;
  Node() : data(0) {
    std::cout << "Node created. " << this <<'\n';
  }

  Node(const Node &n) : data(n.data) {
    std::cout << "Node copied: " << this << "  Source: " << &n << std::endl;
  }

  ~Node(){
    // You don't need data = 0 in destructor
    std::cout << "Node destroyed. " << this <<'\n';
  }

};

int main() {
  std::vector<Node> vec;
  for(int i = 0; i < 2 ; i++) {
    vec.push_back(Node());
  }
  return 0;
}

答案 2 :(得分:12)

向向量添加元素时,可以通过复制构造函数(或移动构造函数)构建新副本。

所以像vec.push_back( *(new Node));这样的行会做两件事。

  • 它使用new构建一个动态分配的新节点。
  • 它将新节点复制到向量中。这意味着通过复制构造函数创建新节点。
  • 原始节点永远不会被释放(这称为内存泄漏

此版本的代码与复制构造函数可能会为您提供一些见解:http://ideone.com/ow5YOI

答案 3 :(得分:4)

你的循环等同于

vec.push_back( *(new Node));
vec.push_back( *(new Node));

会发生什么:

  1. new Node分配内存并创建节点(创建节点
  2. push_back分配新存储空间
  3. push_back使用(隐式)复制构造函数在向量的存储中创建一个Node对象。 (没有打印信息)
  4. 您创建的Node已泄露(存在2个节点,1个无法访问)
  5. new Node分配内存并创建节点(创建节点
  6. push_back分配新存储空间,复制/移动现有元素(不打印任何消息)
  7. push_back删除旧内容(节点已删除
  8. push_back使用(隐式)复制构造函数(未打印任何消息)在向量中创建另一个Node
  9. 您创建的Node已泄露(存在4个节点,2个无法访问)
  10. 当向量超出范围时,它包含的两个副本将被删除。 (节点已删除节点已删除

    你通常会写

    vec.push_back(Node())
    

    vec.emplace_back()
    

    代替。

    如果你想真正了解发生了什么,你应该创建一个非默认的复制构造函数:

    Node(const Node& other){
        data = other.data;
        std::cout << "Node created. " << this
                  << " from " << &other << std::endl;
    }