114

我是一名试图理解 C++ 的 C 程序员。许多教程使用片段演示对象实例化,例如:

Dog* sparky = new Dog();

这意味着稍后您将执行以下操作:

delete sparky;

这是有道理的。现在,在不需要动态内存分配的情况下,是否有任何理由使用上述内容而不是

Dog sparky;

并在 sparky 超出范围时调用析构函数?

谢谢!

4

9 回答 9

168

相反,您应该始终更喜欢堆栈分配,因为根据经验,您永远不应该在用户代码中使用 new/delete。

正如您所说,当变量在堆栈上声明时,它的析构函数会在超出范围时自动调用,这是您跟踪资源生命周期和避免泄漏的主要工具。

所以一般来说,每次你需要分配一个资源,无论是内存(通过调用new)、文件句柄、套接字还是其他任何东西,都将它包装在一个类中,构造函数获取资源,析构函数释放它。然后,您可以在堆栈上创建该类型的对象,并保证您的资源在超出范围时被释放。这样您就不必到处跟踪您的新/删除对,以确保避免内存泄漏。

这个成语最常见的名字是RAII

当您必须在专用 RAII 对象之外分配新的东西时,还可以查看用于在极少数情况下包装结果指针的智能指针类。相反,您将指针传递给智能指针,然后智能指针跟踪其生命周期,例如通过引用计数,并在最后一个引用超出范围时调用析构函数。标准库具有std::unique_ptr简单的基于范围的管理,并std::shared_ptr通过引用计数来实现共享所有权。

许多教程使用片段演示对象实例化,例如...

所以你发现大多数教程都很糟糕。;) 大多数教程教你糟糕的 C++ 实践,包括在不需要时调用 new/delete 来创建变量,并且让你很难跟踪分配的生命周期。

于 2008-12-02T10:15:34.100 回答
20

尽管在分配和自动释放方面将东西放在堆栈上可能是一个优势,但它也有一些缺点。

  1. 您可能不想在堆栈上分配大对象。

  2. 动态调度!考虑这段代码:

#include <iostream>

class A {
public:
  virtual void f();
  virtual ~A() {}
};

class B : public A {
public:
  virtual void f();
};

void A::f() {cout << "A";}
void B::f() {cout << "B";}

int main(void) {
  A *a = new B();
  a->f();
  delete a;
  return 0;
}

这将打印“B”。现在让我们看看使用 Stack 时会发生什么:

int main(void) {
  A a = B();
  a.f();
  return 0;
}

这将打印“A”,这对于熟悉 Java 或其他面向对象语言的人来说可能不直观。原因是您不再有指向实例的指针B。相反,B会创建一个 的实例并将其复制到atype 的变量中A

有些事情可能会以不直观的方式发生,尤其是当您不熟悉 C++ 时。在 C 中,你有你的指针,就是这样。你知道如何使用它们,而且它们总是一样的。在 C++ 中,情况并非如此。想象一下,当您在此示例中使用 a 作为方法的参数时会发生什么 - 事情变得更加复杂,如果a是类型AA*A&至(通过引用调用),它确实会产生巨大的差异。许多组合是可能的,它们的行为都不同。

于 2013-06-29T11:05:18.297 回答
13

好吧,使用指针的原因与在 C 中使用 malloc 分配的指针的原因完全相同:如果您希望对象的寿命比变量长!

如果可以避免的话,甚至强烈建议不要使用 new 运算符。特别是如果您使用异常。一般来说,让编译器释放你的对象要安全得多。

于 2008-12-02T09:25:53.810 回答
13

我从不太了解 & address-of 运算符的人那里看到了这种反模式。如果他们需要使用指针调用函数,他们将始终在堆上分配,以便获得指针。

void FeedTheDog(Dog* hungryDog);

Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;

Dog goodDog;
FeedTheDog(&goodDog);
于 2008-12-02T17:26:27.460 回答
7

将堆视为非常重要的不动产并非常明智地使用它。基本的经验法则是尽可能使用堆栈并在没有其他方法时使用堆。通过在堆栈上分配对象,您可以获得许多好处,例如:

(1)。您不必担心出现异常时堆栈展开

(2)。您不必担心由于堆管理器分配的空间过多而导致的内存碎片。

于 2008-12-02T17:06:25.893 回答
5

我担心的唯一原因是 Dog 现在分配在堆栈上,而不是堆上。所以如果 Dog 的大小是兆字节,你可能会遇到问题,

如果您确实需要走新/删除路线,请注意例外情况。因此,您应该使用 auto_ptr 或 boost 智能指针类型之一来管理对象的生命周期。

于 2008-12-02T09:28:52.883 回答
1

当您可以在堆栈上分配时,没有理由(在堆上)新建(除非由于某种原因您有一个小堆栈并且想要使用堆。

如果您确实想在堆上分配,您可能需要考虑使用标准库中的 shared_ptr(或其变体之一)。一旦对 shared_ptr 的所有引用都不存在,这将为您处理删除操作。

于 2008-12-02T09:30:51.383 回答
0

还有一个其他人没有提到的原因,您可能会选择动态创建对象。动态的、基于堆的对象允许您利用多态性

于 2013-03-13T11:50:34.220 回答
-4

我在 Visual Studio 中遇到了同样的问题。你必须使用:

yourClass->classMethod();

而不是:

yourClass.classMethod();

于 2012-08-12T23:31:47.957 回答