为了分配动态内存,我一直在 C++ 中使用向量。但是最近,在阅读一些源代码时,我发现了“new int[size]”的用法,并在一些研究中发现它也分配了动态内存。
谁能给我建议哪个更好?我从算法和 ICPC 的角度来看?
原文由 varagrawal 发布,翻译遵循 CC BY-SA 4.0 许可协议
为了分配动态内存,我一直在 C++ 中使用向量。但是最近,在阅读一些源代码时,我发现了“new int[size]”的用法,并在一些研究中发现它也分配了动态内存。
谁能给我建议哪个更好?我从算法和 ICPC 的角度来看?
原文由 varagrawal 发布,翻译遵循 CC BY-SA 4.0 许可协议
在几乎任何情况下, std::vector
都是可取的。它有一个析构函数来释放内存,而手动管理的内存必须在完成后显式删除。很容易引入内存泄漏,例如,如果某些东西在被删除之前抛出异常。例如:
void leaky() {
int * stuff = new int[10000000];
do_something_with(stuff);
delete [] stuff; // ONLY happens if the function returns
}
void noleak() {
std::vector<int> stuff(10000000);
do_something_with(stuff);
} // Destructor called whether the function returns or throws
如果您需要调整数组大小或复制数组,它也更方便。
偏爱原始数组的唯一原因是如果您有极端的性能或内存限制。 vector
是比指针更大的对象(包含大小和容量信息);它有时会对其对象进行值初始化,而原始数组将默认初始化它们(对于普通类型,这意味着它们未初始化)。
在这些问题可能很重要的极少数情况下,您应该考虑 std::unique_ptr<int[]>
;它有一个析构函数,可以防止内存泄漏,并且与原始数组相比没有运行时开销。
原文由 Mike Seymour 发布,翻译遵循 CC BY-SA 3.0 许可协议
3 回答2k 阅读✓ 已解决
2 回答3.9k 阅读✓ 已解决
2 回答3.2k 阅读✓ 已解决
1 回答3.2k 阅读✓ 已解决
1 回答2.7k 阅读✓ 已解决
3 回答3.5k 阅读
1 回答3.3k 阅读
总是 喜欢 标准容器。它们具有明确定义的复制语义,异常安全,并且可以正确释放。
当你手动分配时,你必须保证发布代码被执行,并且作为成员,你必须编写正确的复制赋值和复制构造函数,在发生异常的情况下做正确的事情而不泄漏。
手动的:
如果我们希望我们的变量只有它们真正需要的范围,它就会变得很臭:
要不就:
对于具有复制语义的类来说,实现它是一件很可怕的事情。复制可能会抛出,分配可能会抛出,理想情况下,我们需要事务语义。一个例子会打破这个答案。
好的。
可复制类 - 手动资源管理与容器
我们有这个看起来很无辜的班级。事实证明,这是非常邪恶的。我想起了美国麦基的爱丽丝:
泄漏。大多数初学者 C++ 程序员都认识到缺少删除。添加它们:
未定义的行为。中级 C++ 程序员认识到使用了错误的删除操作符。解决这个问题:
如果类被复制,糟糕的设计、泄漏和双重删除就会潜伏在那里。复制本身很好,编译器干净地为我们复制了指针。但是编译器不会发出代码来创建 数组 的副本。
稍微有经验的 C++ 程序员认识到三法则没有得到尊重,这意味着如果您明确编写了析构函数、复制赋值或复制构造函数中的任何一个,您可能还需要写出其他的,或者在没有实现的情况下将它们设为私有:
正确的。 … 前提是您可以保证永远不会耗尽内存,并且 Bar 和 Frob 都不会在复制时失败。乐趣从下一节开始。
编写异常安全代码的仙境。
建造
f_
初始化失败怎么办?Frobs
都被销毁。想象一下 20Frob
被构建了,第 21 个将失败。然后,按照 LIFO 顺序,前 20 个Frob
将被正确销毁。而已。意思是:你现在有 64 个僵尸
Bars
。Foos
对象本身永远不会复活,因此不会调用它的析构函数。如何使这个异常安全?
构造函数应该总是 完全成功或完全 _失败_。它不应该是半生半死的。解决方案:
复印
记住我们对复制的定义:
Bar
将不得不在后台复制大量资源。它可能会失败,它会。这意味着我们的
Foo
现在处于不一致和不可预测的状态。为了赋予它事务语义,我们需要完全或不完全建立新状态,然后使用无法抛出的操作将新状态植入我们的Foo
中。最后,我们需要清理临时状态。解决方案是使用复制和交换习语( http://gotw.ca/gotw/059.htm )。
首先,我们改进我们的复制构造函数:
然后,我们定义一个非抛出的交换函数
现在我们可以使用我们新的异常安全复制构造函数和异常安全交换函数来编写异常安全复制赋值运算符:
发生了什么?首先,我们建立新的存储并将 rhs’ 复制到其中。这可能会抛出,但如果发生了,我们的状态不会改变并且对象仍然有效。
然后,我们用临时的胆量交换我们的胆量。临时获得不再需要的东西,并在范围结束时释放这些东西。我们有效地将 tmp 用作垃圾箱,并正确选择 RAII 作为垃圾收集服务。
您可能想查看 http://gotw.ca/gotw/059.htm 或阅读
Exceptional C++
以了解有关此技术和编写异常安全代码的更多详细信息。把它放在一起
不能扔或不准扔的总结:
最后是我们精心制作的、异常安全的、更正后的 Foo 版本:
将其与我们最初的、看起来天真烂漫的代码进行比较:
您最好不要向其中添加更多变量。迟早,你会忘记在某个地方添加正确的代码,你的整个班级都会生病。
或使其不可复制。
对于某些类,这是有道理的(例如流;要共享流,用 std::shared_ptr 明确表示),但对于许多类来说,它没有。
真正的解决方案。
此类具有干净的复制语义,是异常安全的(请记住:异常安全并不意味着不抛出,而是不泄漏并且可能具有事务语义),并且不会泄漏。