我是一名试图理解 C++ 的 C 程序员。许多教程使用片段演示对象实例化,例如:
Dog* sparky = new Dog();
这意味着稍后您将执行以下操作:
delete sparky;
这是有道理的。现在,在不需要动态内存分配的情况下,是否有任何理由使用上述内容而不是
Dog sparky;
并在 sparky 超出范围时调用析构函数?
谢谢!
我是一名试图理解 C++ 的 C 程序员。许多教程使用片段演示对象实例化,例如:
Dog* sparky = new Dog();
这意味着稍后您将执行以下操作:
delete sparky;
这是有道理的。现在,在不需要动态内存分配的情况下,是否有任何理由使用上述内容而不是
Dog sparky;
并在 sparky 超出范围时调用析构函数?
谢谢!
相反,您应该始终更喜欢堆栈分配,因为根据经验,您永远不应该在用户代码中使用 new/delete。
正如您所说,当变量在堆栈上声明时,它的析构函数会在超出范围时自动调用,这是您跟踪资源生命周期和避免泄漏的主要工具。
所以一般来说,每次你需要分配一个资源,无论是内存(通过调用new)、文件句柄、套接字还是其他任何东西,都将它包装在一个类中,构造函数获取资源,析构函数释放它。然后,您可以在堆栈上创建该类型的对象,并保证您的资源在超出范围时被释放。这样您就不必到处跟踪您的新/删除对,以确保避免内存泄漏。
这个成语最常见的名字是RAII
当您必须在专用 RAII 对象之外分配新的东西时,还可以查看用于在极少数情况下包装结果指针的智能指针类。相反,您将指针传递给智能指针,然后智能指针跟踪其生命周期,例如通过引用计数,并在最后一个引用超出范围时调用析构函数。标准库具有std::unique_ptr
简单的基于范围的管理,并std::shared_ptr
通过引用计数来实现共享所有权。
许多教程使用片段演示对象实例化,例如...
所以你发现大多数教程都很糟糕。;) 大多数教程教你糟糕的 C++ 实践,包括在不需要时调用 new/delete 来创建变量,并且让你很难跟踪分配的生命周期。
尽管在分配和自动释放方面将东西放在堆栈上可能是一个优势,但它也有一些缺点。
您可能不想在堆栈上分配大对象。
动态调度!考虑这段代码:
#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
会创建一个 的实例并将其复制到a
type 的变量中A
。
有些事情可能会以不直观的方式发生,尤其是当您不熟悉 C++ 时。在 C 中,你有你的指针,就是这样。你知道如何使用它们,而且它们总是一样的。在 C++ 中,情况并非如此。想象一下,当您在此示例中使用 a 作为方法的参数时会发生什么 - 事情变得更加复杂,如果a
是类型A
或A*
什A&
至(通过引用调用),它确实会产生巨大的差异。许多组合是可能的,它们的行为都不同。
好吧,使用指针的原因与在 C 中使用 malloc 分配的指针的原因完全相同:如果您希望对象的寿命比变量长!
如果可以避免的话,甚至强烈建议不要使用 new 运算符。特别是如果您使用异常。一般来说,让编译器释放你的对象要安全得多。
我从不太了解 & address-of 运算符的人那里看到了这种反模式。如果他们需要使用指针调用函数,他们将始终在堆上分配,以便获得指针。
void FeedTheDog(Dog* hungryDog);
Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;
Dog goodDog;
FeedTheDog(&goodDog);
将堆视为非常重要的不动产并非常明智地使用它。基本的经验法则是尽可能使用堆栈,并在没有其他方法时使用堆。通过在堆栈上分配对象,您可以获得许多好处,例如:
(1)。您不必担心出现异常时堆栈展开
(2)。您不必担心由于堆管理器分配的空间过多而导致的内存碎片。
我担心的唯一原因是 Dog 现在分配在堆栈上,而不是堆上。所以如果 Dog 的大小是兆字节,你可能会遇到问题,
如果您确实需要走新/删除路线,请注意例外情况。因此,您应该使用 auto_ptr 或 boost 智能指针类型之一来管理对象的生命周期。
当您可以在堆栈上分配时,没有理由(在堆上)新建(除非由于某种原因您有一个小堆栈并且想要使用堆。
如果您确实想在堆上分配,您可能需要考虑使用标准库中的 shared_ptr(或其变体之一)。一旦对 shared_ptr 的所有引用都不存在,这将为您处理删除操作。
还有一个其他人没有提到的原因,您可能会选择动态创建对象。动态的、基于堆的对象允许您利用多态性。
我在 Visual Studio 中遇到了同样的问题。你必须使用:
yourClass->classMethod();
而不是:
yourClass.classMethod();