Answers:
相反,您应该始终偏向于堆栈分配,以作为经验法则,您的用户代码中永远不应包含新内容/删除内容。
就像您说的那样,当在堆栈上声明变量时,超出范围时将自动调用其析构函数,这是您跟踪资源寿命并避免泄漏的主要工具。
因此,通常,每次需要分配资源时,无论是内存(通过调用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”。现在让我们看看使用堆栈时会发生什么:
int main(void) {
A a = B();
a.f();
return 0;
}
这将打印“ A”,这对于熟悉Java或其他面向对象的语言的人可能不直观。原因是您不再有指向该实例的指针B
。而是B
创建一个实例并将其复制到a
类型为的变量A
。
有些事情可能会凭直觉发生,特别是当您不熟悉C ++时。在C中,您就有了指针,仅此而已。您知道如何使用它们,并且它们总是一样。在C ++中,情况并非如此。试想会发生什么,当你在这个例子中使用作为一个方法的参数-事情变得更复杂,但它使一个巨大的区别,如果a
是类型A
或者A*
甚至A&
(呼叫通过引用)。许多组合都是可能的,并且它们的行为都不同。
我已经从不太了解&地址运算符的人那里看到了这种反模式。如果他们需要使用指针来调用函数,则它们将始终在堆上分配,以便获得指针。
void FeedTheDog(Dog* hungryDog);
Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;
Dog goodDog;
FeedTheDog(&goodDog);
当您可以在堆栈上进行分配时,没有任何理由(在堆上)新建(除非出于某种原因,您的堆栈很小并且想要使用堆。
如果您确实想在堆上进行分配,则可能要考虑使用标准库中的shared_ptr(或其变体之一)。一旦所有对shared_ptr的引用都不存在,将为您执行删除操作。
还有一个其他原因,您可能选择动态创建对象,这是其他人没有提到的。动态的,基于堆的对象使您可以利用多态。