C++ 为什么叫毁灭?

C++ 为什么叫毁灭?,c++,C++,我不知道为什么在接下来的时间点调用向量中对象的销毁 class Something { public: Something() {} ~Something() { cout << "destruction called" << endl; } }; int main() { std::vector<Something> vec; Something sth1 = Something(); Something

我不知道为什么在接下来的时间点调用向量中对象的销毁

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的销毁。为什么?sth1不应该保存在vec[0]中吗?

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

该向量将内存分配为一个连续的块,即一个动态数组,用于保存放入其中的对象。因为它事先不知道它将增长到多大,所以当您向它添加更多元素时,它的容量会逐渐增长

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

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

还要注意,push_back总是复制被推入的元素,它不保留引用;这允许它所持有的对象在堆栈上的生命周期之后在向量中保持不变。因此在main的末尾,将有四个对析构函数的调用:向量中的两个对象各调用一个,sth1和sth2从堆栈中弹出时各调用一个

1) Something sth1 = Something();
2) Something sth2 = Something();
3) vec.push_back(sth1);
4) vec.push_back(sth2);
5) vec.clear(); 
在1时,您将在堆栈上创建sth1—它的生存期将一直到堆栈帧作为主出口被销毁为止。标准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_backsh1可以在堆上分配一些内存,然后复制并在其中构造一个与sth1值相同的对象。注意,这不是sth1本身,而是执行推回时sth1值的副本。对这些副本的更新以及对sth1和sth2的更新都是独立的

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

5时,sth1和sth2的副本被销毁

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]

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

您可以创建一个向量,然后将_向后推&sth1和&sth2 只要sth1和sth2的生命周期(即周围作用域的生命周期)是正确的,在这里,main本身的生命周期跨越了指针可能被解引用的时间 在指向实际sth1和sth2对象的指针之后-而不是使用push_back时创建的副本-意味着对sth1和sth2的任何更新都将通过指针可见,并且通过指针的更新实际上会影响sth1和sth2 如果希望向量已知的对象的生存期超出基于堆栈的sth1和sth2的范围所暗示的生存期,可以:

在堆ala某物上分配sth1和sth2*sth1=新某物; 将指针保持在向量中 如果不使用智能指针,例如boost::shared_指针,则需要在从向量中删除指针或清除向量之前删除每个指针,以避免内存泄漏。
然后,如果我希望sth1存储在vector中,直到调用clear,我该怎么办。我知道reserve可能会有帮助,但是如果我不知道确切的容量怎么办?@CDBean:不管你做什么,你都不能将实际的sth1对象存储在vector中;向量总是以这样或那样的方式保留副本。正如我在回答中提到的,push_back首先创建一个副本。如果你真的想,你可以切换到使用向量;然后只存储指向原始对象的指针,但您必须小心,它们在对象的整个生命周期内保持有效vector@CDBean:你不能,我也不明白你为什么要这么做。复印件可以。C++0x也添加了移动。为什么事情不好?如果要避免复制,请使用智能指针,如unique_ptr或shared_ptr。不是auto_ptr。我很确定声明期间的初始化总是使用复制构造函数而不是赋值运算符,但您关于可能的低效率的观点仍然是正确的
ds@Cameron:没有。。。大约一周前,我们在这里讨论了这个问题,并参考了标准。。。编译器可以免费使用elide,但不需要。@Cameron:如果你好奇的话,它是12.8.15。。。措辞是允许的,而不是必需的/应该的等等。你是对的,这里允许编译器优化掉临时对象,但如果没有,则将调用复制构造函数,而不是赋值运算符12.6.1