“正确”的释放std::vector对象的方法

46

第一个解决方案是:

std::vector<int> *vec = new std::vector<int>;
assert(vec != NULL);
// ...
delete vec;

一种替代方案是:

std::vector<int> v;
//...
vec.clear();
vec.swap(std::vector<int>(vec));

第二种解决方案有点技巧 --- 什么是“正确”的方法?

更新:

我知道一旦它离开堆栈,析构函数将被调用,我想知道其他方法。


12
为什么要加上assert(vec != NULL)呢?因为new操作不会返回NULL - James McNellis
6
一些旧的实现(或没有异常处理机制的编译器!) - Martin Beckett
5
第二种(也是唯一正确的)变体显然不需要 vec.clear(); - sbi
2
@sbi:正如它所写的那样,它使用了“复制并交换”技巧来保留数据并释放任何额外的容量。要释放所有内存,vec = std::vector<int>();就足够了。 - Mike Seymour
3
虽然我不反对,但我认为测试(或记录)基本实现功能是有些奇怪的。在调用vec.clear()之后是否应该assert(vec.empty()),告诉读者“我知道前一行代码已经清空了向量”? - James McNellis
显示剩余6条评论
12个回答

56
最简单、最可靠的释放向量的方法是在堆栈上声明它,然后什么也不做。
void Foo() {
  std::vector<int> v;
  ...
}

C++保证在方法执行时会调用v的析构函数。 std::vector的析构函数将确保释放其分配的任何内存。只要vector<T>T类型具有适当的C++释放语义,一切都会很好。


5
@Jacob:那你为什么还想尝试其他的方法?没有必要,也不符合惯用法,去尝试释放一个向量。 - Billy ONeal
@Billy:你说得对,我想不出还需要其他什么情况。我重写了代码,使得巨大的向量被使用并在一个单独的函数中被丢弃。 - Jacob
1
请注意,您只能使用std::vector来完成此操作,因为尽管对象在堆栈上分配,但实际数据位于堆上。 std:: 可以帮助您处理内存的分配和释放。 - Martin Beckett
13
你不一定需要将使用向量的代码移动到另一个函数中。如果你用{...}括起来它所在的代码块,析构函数会在该代码块结束时被调用。 - Pedro d'Aquino
如果您的控件需要比代码块更复杂,但仍希望使用RAII,您可以使用一个具有一种类型的boost::optionalboost::variant,或者使用std::aligned_storage_t并在方便时手动调用析构函数。或者,您可以使用std::unique_ptr,但您会失去堆栈分配,会导致内存碎片化,锁定全局malloc互斥锁,并且性能下降。 - v.oddou

22

释放 vector 中所有存储空间的最简单方法,而不销毁 vector 对象本身,是

vec = std::vector<int>();

你的第二种变体将产生相同的效果,但是它在过程中增加了更多的复杂性。"拷贝并交换"技巧会释放vector中的任何额外容量,并且如果它包含一些您想要保留的数据,则可能会有用。如果没有数据,则不需要进行拷贝或交换。


这与 vec.resize(0) 有什么不同吗? - pretzlstyle
2
...还是更清晰的vec.clear()呢?(无恶意) - L. F.
4
是的,它们不同,因为resize和clear都会执行相同的操作 - 它们清除向量的内容(调用成员析构函数等),但不会释放向量已经预留的内存。也就是说,你改变了向量的size(),但没有改变它的capacity()。创建一个新的向量并将其赋值给旧向量会释放原始向量的内存。 - devnullicus
我认为这种风格比所示的交换方法更易读。JMO - Morc

13
std::vector<int> vi;
/*push lots of stuff into the vector*/

// clean it up in C++03
// no need to clear() first
std::vector<int>().swap(vi);

// clean it up in C++0x
// not a one liner, but much more idiomatic
vi.clear();
vi.shrink_to_fit();

6
shrink_to_fit 只是一个提示,实际收缩并不保证。 - fredoverflow
3
shrink_to_fit这个函数之所以是一个提示,是为了允许对内存进行高级操作。如果向量使用类似于小字符串优化(不太可能)或者向量总是从池中分配内存并且最小大小大于当前向量大小,则收缩大小就没有意义。但是可以假定 shrink_to_fit() 会尽可能地使向量变小。 - deft_code
8
如果向量使用不同的分配器,swap技巧就行不通了!我刚刚吃了这个亏。这可能是一个相当罕见的情况,所以我不知道是否值得将其突出地提到比这个评论更多。 - entheh
我认为应该提到这个为什么起作用。 它与复制并交换惯用法有关。 基本上,通过 std :: vector <int>().swap(vi),您创建了一个空向量的右值,然后将其与原始向量交换。 右值的范围仅限于此语句,因此在交换之后立即超出范围,并且其析构函数会释放与其关联的数据。 - plasmacel

3

我同意Mike Seymour的看法,你可以试一下,然后你会发现最后一个工作得很好。

const int big_size = 10000;
vector<double> v( big_size );
cout << "Before clearing, the capacity of the vector is "
  << v.capacity() << " and its size is " << v.size();
v.clear();
cout << "\nAfter clearing, the capacity of the vector is "
  << v.capacity() << " and its size is " << v.size();
vector<double>().swap( v );

cout << "\nAfter swapping, the capacity of the vector is "
  << v.capacity() << " and its size is " << v.size();

vector<double> v1( big_size );
v1 = vector<double>();
cout << "\n After vector<double>();, the capacity of the vector is "
  << v1.capacity() << " and its size is " << v1.size();

你能提供一下 Mike Seymour 参考资料的链接吗? - unicorn2
@unicorn2 我认为这是其中一个答案:https://dev59.com/I3A75IYBdhLWcg3w4tR7#3055404 - Ziyuan

2
我的猜测是,你有一个暂时包含大量数据的向量。一旦向量被清除,它仍将占用所有这些内存。在你完成与函数/对象的工作之后,你想释放这个内存。
以下是按可取性递减的解决方案:
1. 重构代码,使使用代码的向量位于自己的块/函数/对象中,这样它就会自然地被销毁。 2. 使用交换技巧,这样你就不必担心在所有情况下都要确保向量被释放。它的寿命将与你所在的对象/函数绑定。 3. new/delete向量。这将释放比前一种方法多一点的内存,但更难确保没有泄漏的内存。
交换和删除之间唯一的技术差异是基本向量本身没有被销毁。这是一个很小的开销,不值得担心(只要最终销毁向量即可)。
更重要的考虑是哪个能更容易地编写正确的代码,我认为交换胜过删除,但比将向量移动到其他地方要差。

1

除非确实需要,否则不要使用内存分配函数。如果您的类需要一个向量,那么直接添加std::vector成员即可,这里不需要进行内存分配。

在需要动态向量的情况下,像第一个示例中一样分配和删除是100%正确的。

在第二个示例中,严格来说,不需要调用std::swap,因为clear方法将清除向量,使其为空。可能存在的一个问题是,不能保证向量实际上会释放内存并将其返回给操作系统(或运行时)。向量可能会保留已分配的内存,以防您在清除后立即填充向量。调用std::swap可能是“强制”向量释放其内部数据的技巧,但不能保证实际发生这种情况。


0

我不确定为什么你的第二个示例使用了一个复制构造函数来创建临时对象,而不是使用默认构造函数。这样可以省去 .clear() 代码行。

你可以将这个方法泛化到任何对象上,即使它不是一个容器。我在这里假设 std::swap 已经被专门用于调用 vector::swap。

template<typename T>
void ResetToDefault(T & value)
{
    std::swap(T(), value);
}

std::vector<int> vec;  
//...  
ResetToDefault(vec);

1
在C++03中,std::swap(T(), value)无法编译,因为T()是一个rvalue,不能被绑定到引用。 - fredoverflow

0

如果向量确实需要在堆上,请不要忘记:

std::auto_ptr<std::vector<int> > vec(new std::vector<int>);

特别适用于像这样的代码:

std::auto_ptr<std::vector<int> > vec(vectorFactoryFunction());

0

delete释放内存,内存随后可供下一个对象使用,但向量已经消失。

第二个技巧释放任何多余的内存,但保持向量完整,但为空。


0

虽然两者看起来都可以工作,但我认为没有理由不直接调用指针上的delete。向量应该有一个析构函数被调用,它将处理其他所有事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接