5

在学习不同的语言时,我经常看到动态分配的对象,最常见的是在 Java 和 C# 中,如下所示:

functionCall(new className(initializers));

我知道这在内存管理语言中是完全合法的,但是这种技术可以在 C++ 中使用而不会导致内存泄漏吗?

4

8 回答 8

12

您的代码是有效的(假设 functionCall() 实际上保证指针被删除),但它很脆弱,会使大多数 C++ 程序员的警钟响起。

您的代码存在多个问题:

  • 首先,谁拥有指针?谁负责释放它?调用代码无法做到这一点,因为您不存储指针。这意味着被调用的函数必须这样做,但是对于查看该函数的人来说并不清楚。同样,如果我从其他地方调用代码,我当然不希望函数在我传递给它的指针上调用 delete!
  • 如果我们使您的示例稍微复杂一些,它可能会泄漏内存,即使被调用的函数调用 delete 也是如此。假设它看起来像这样: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. 您在堆栈上分配它,并让它在内部处理其内存分配。

于 2008-12-29T21:02:30.203 回答
6

是的,只要你在函数内部释放内存。但这绝不是 C++ 的最佳实践。

于 2008-12-29T20:18:15.177 回答
5

这取决于。

这会将内存的“所有权”传递给 functionCAll()。它要么需要释放对象,要么保存指针,以便以后释放。像这样传递原始指针的所有权是将内存问题构建到代码中的最简单方法之一——泄漏或双重删除。

于 2008-12-29T20:19:18.560 回答
3

在 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 >>
}
于 2008-12-29T23:28:07.210 回答
1

如果您正在调用的函数具有接受所有权语义,则它是安全的。我不记得我需要这个的时间,所以我认为它不寻常。

如果函数以这种方式工作,它应该将其参数作为智能指针对象,以便意图明确;IE

void functionCall(std::auto_ptr<className> ptr);

而不是

void functionCall(className* ptr);

这使得所有权的转移变得明确,当函数的执行超出范围时,调用函数将处理 ptr 指向的内存。

于 2008-12-29T20:55:06.520 回答
0

这适用于在堆栈上创建的对象,但不适用于 C++ 中的常规指针。

一个自动指针也许能够处理它,但我还没有弄乱它们足以知道。

于 2008-12-29T20:20:27.217 回答
0

一般来说,不,除非你泄漏内存。事实上,在大多数情况下,这是行不通的,因为

new T();

在 C++ 中是 T*,而不是 T(在 C# 中,new T() 返回 T)。

于 2008-12-29T20:23:03.560 回答
0

看看Smart PointersC 和 C++ 的垃圾收集器

于 2008-12-29T20:36:29.460 回答