【发布时间】:2008-12-29 20:16:02
【问题描述】:
在学习不同的语言时,我经常看到动态分配的对象,最常见的是 Java 和 C#,如下所示:
functionCall(new className(initializers));
我知道这在内存管理语言中是完全合法的,但是这种技术可以在 C++ 中使用而不会导致内存泄漏吗?
【问题讨论】:
标签: c++ memory-leaks
在学习不同的语言时,我经常看到动态分配的对象,最常见的是 Java 和 C#,如下所示:
functionCall(new className(initializers));
我知道这在内存管理语言中是完全合法的,但是这种技术可以在 C++ 中使用而不会导致内存泄漏吗?
【问题讨论】:
标签: c++ memory-leaks
您的代码是有效的(假设 functionCall() 实际上保证指针被删除),但它很脆弱,会让大多数 C++ 程序员的警钟敲响。
您的代码存在多个问题:
functionCall(new className(initializers), new className(initializers)); 假设第一个分配成功,但是第二个抛出异常(可能是内存不足,或者类构造函数抛出异常)。 functionCall 永远不会被调用,并且无法释放内存。简单(但仍然很混乱)的解决方案是先分配内存,存储指针,然后在声明的同一范围内释放它(因此调用函数拥有内存):
className* p = new className(initializers);
functionCall(p);
delete p;
但这仍然是一团糟。如果 functionCall 抛出异常怎么办?然后 p 不会被删除。除非我们在整个事情上添加一个 try/catch,但是,天哪,这很混乱。 如果函数变得有点复杂,并且可能在 functionCall 之后但在 delete 之前返回怎么办?哎呀,内存泄漏。无法维持。错误的代码。
所以一个不错的解决方案是使用智能指针:
boost::shared_ptr<className> p = boost::shared_ptr<className>(new className(initializers));
functionCall(p);
现在处理内存的所有权。 shared_ptr 拥有内存,并保证它会被释放。当然,我们可以使用 std::auto_ptr 代替,但 shared_ptr 实现了您通常期望的语义。
请注意,我仍然在单独的行上分配内存,因为在进行函数调用时在同一行上进行多次分配的问题仍然存在。其中一个可能仍然抛出,然后你已经泄漏了内存。
智能指针通常是您处理内存管理所需的绝对最小值。 但通常,不错的解决方案是编写自己的 RAII 类。
className 应该在堆栈上分配,并在其构造函数中,使用new 进行必要的分配。在其析构函数中,它应该释放该内存。这样就可以保证不会发生内存泄漏,并且可以让函数调用变得如此简单:
functionCall(className(initializers));
C++ 标准库是这样工作的。 std::vector 就是一个例子。你永远不会用new 分配一个向量。您在堆栈上分配它,并让它在内部处理其内存分配。
【讨论】:
是的,只要你在函数内部释放内存。但这绝不是 C++ 的最佳实践。
【讨论】:
视情况而定。
这会将内存的“所有权”传递给 functionCAll()。它要么需要释放对象,要么保存指针,以便以后释放。像这样传递原始指针的所有权是将内存问题构建到代码中的最简单方法之一——泄漏或双重删除。
【讨论】:
在 C++ 中,我们不会像那样动态创建内存。
相反,您将创建一个临时堆栈对象。
如果您希望对象的生命周期大于对函数的调用,则只需要通过 new 创建一个堆对象。在这种情况下,您可以将 new 与智能指针结合使用(有关示例,请参见其他答案)。
// No need for new or memory management just do this
functionCall(className(initializers));
// This assumes you can change the functionCall to somthing like this.
functionCall(className const& param)
{
<< Do Stuff >>
}
如果你想传递一个非 const 引用,那么这样做:
calssName tmp(initializers);
functionCall(tmp);
functionCall(className& param)
{
<< Do Stuff >>
}
【讨论】:
如果您调用的函数具有接受所有权语义,则它是安全的。我不记得有什么时候需要这个,所以我认为这很不寻常。
如果函数以这种方式工作,它应该将其参数作为智能指针对象,以便意图明确;即
void functionCall(std::auto_ptr<className> ptr);
而不是
void functionCall(className* ptr);
这使得所有权的转移显式,当函数的执行超出范围时,调用函数将释放 ptr 指向的内存。
【讨论】:
这适用于在堆栈上创建的对象,但不适用于 C++ 中的常规指针。
一个自动指针也许可以处理它,但我还没有弄乱它们足以知道。
【讨论】:
一般来说,不会,除非您想要泄漏内存。事实上,在大多数情况下,这是行不通的,因为
new T();
在 C++ 中是 T*,而不是 T(在 C# 中,new T() 返回 T)。
【讨论】: