为什么叫破坏?

时间:2011-03-08 02:10:31

标签: c++

我不知道为什么在下一个时间点调用vector中对象的破坏。

class Something
{
public:
    Something() {}
    ~Something()    { cout << "destruction called" << endl; }
};

int main()
{
    std::vector<Something> vec;
    Something sth1 = Something();   
    Something sth2 = Something();
    vec.push_back(sth1);
    vec.push_back(sth2);
    vec.clear();
}

在我按下sth2后,调用sth1的破坏。为什么?不应该保留在vec [0]中吗?

3 个答案:

答案 0 :(得分:10)

因为vector必须调整其容量才能存储两个元素而不是一个元素。它分配一个新缓冲区,它将旧缓冲区复制到新缓冲区,删除旧缓冲区,然后添加新对象。

答案 1 :(得分:2)

向量在运行中分配内存(作为连续的块,即数组)以保存放入其中的对象。由于事先并不知道它会增长多少,因此随着你添加更多元素,它会逐渐增加容量。

每次必须增长以适应新元素时,它必须分配一个足够大的新数组来存储所有内容(通常是旧数组的两倍,以避免过于频繁地调整大小);然后它必须将旧数组中的所有元素复制到新数组。然后释放旧数组,销毁它包含的对象。

vec[0] 包含与sth1完全相同的对象,而不是同一个对象(它将是在调整大小期间使用复制构造函数创建的副本矢量)。

另请注意,push_back()始终复制正在推送的元素(它不保留引用);这允许它所持有的对象在它们的生命周期中持久存在于堆栈中。因此,在main()的末尾,将对析构函数进行四次调用:一个用于向量中的两个对象中的每一个,一个用于sth1sth2,因为它们被弹出堆栈。

答案 2 :(得分:1)

1) Something sth1 = Something();
2) Something sth2 = Something();
3) vec.push_back(sth1);
4) vec.push_back(sth2);
5) vec.clear(); 

1)你正在堆栈上创建sth1 - 它的生命周期将一直存在,直到堆栈帧被解除,main()退出。允许编译器 (标准12.8.15)忽略赋值,使构造等同于:

Something sth1;

但是,编译器也可以更盲目地跟踪你的代码并且可能效率低下:

1a) create a temporary Something()
1a) copy-construct sth1 on the stack, lifetime as per main(), copying the value of the temporary
1c) destroy the temporary

总结:不要担心赋值:与C#或Java不同,对象是在C ++中隐式构造的。

在3) - 可以期望vec.push_back(sth1)在堆上分配一些内存,然后使用与sth1相同的值复制构造其中的对象。请注意,这不是不是 sth1本身,而是执行sth1push_back的值的副本。这些副本的更新以及sth1sth2的更新都是独立的。

在4)向量可能需要也可能不需要调整其堆内存的大小以便为sth1的副本腾出空间:如果它这样做,那么[0]处的现有值 - 这是从sth1复制构造的但实际上sth1,将被复制到新的内存区域,然后将在即将释放的内存区域中调用析构函数。

在5)sth1sth2副本被销毁

5之后,sth1和sth2自己超出范围作为主要退出,并调用它们的析构函数。

如果您希望看到这种情况发生,我建议您为班级添加更好的跟踪:

struct Something
{
    Something() { std::cout << "Something(this " << (void*)this << ")\n"; }
    ~Something() { std::cout << "~Something(this " << (void*)this << ")\n"; }
    Something(const Something& rhs)
    { std::cout << "Something(this " << (void*)this
                << ", rhs " << (void*)rhs << ")\n"; }
    Something& operator=(const Something& rhs)
    { std::cout << "operator=(this " << (void*)this
                << ") = " << (void*)rhs << '\n'; }
};

您还可以在每个(void*)&vec[0]之后打印push_back

从您的尝试中退后一步,看看是否/如何消除复制:

  • 您可以创建vector<Something*>,然后push_back(&sth1)&sth2
    • 只要sth1sth2的生命周期(这是周围范围的生命周期 - 这里是main()本身的生命周期)跨越指针的生命周期就可以了。可以被解除引用
    • 指向实际的sth1sth2对象的指针 - 而不是使用push_back()时生成的副本 - 表示对sth1和{{的任何更新1}}将通过指针显示,通过指针的更新实际上会影响sth2sth1

如果您希望向量已知的对象的生命周期超出基于堆栈的sth2sth1周围范围所暗示的范围,您可以:

  • 在堆ala sth2
  • 上分配sth1sth2
  • 在向量中保留指针
    • 如果你不使用智能指针(例如boost :: shared_pointer),那么在从向量中移除它或清除向量之前你需要删除每个指针(以避免内存泄漏)。