两者之间有一个重要的区别。
所有未分配的new它的行为与C#中的值类型非常相似(人们经常说,这些对象是在堆栈上分配的,这可能是最常见/最明显的情况,但并不总是正确的)。更准确地说,未使用new有自动存储持续时间分配的一切new在堆上分配,并返回指向堆的指针,与C#中的引用类型完全相同。
在堆栈上分配的任何东西都必须有一个常数大小,在编译时确定(编译器必须正确地设置堆栈指针,或者如果对象是另一个类的成员,则必须调整另一个类的大小)。这就是为什么C#中的数组是引用类型。它们必须是,因为对于引用类型,我们可以在运行时决定需要多少内存。同样的道理也适用于这里。只有具有恒定大小(在编译时可以确定的大小)的数组才能以自动存储时间分配(在堆栈上)。必须在堆上分配动态大小的数组,方法是调用new.
(这就是与C#的任何相似之处)
现在,堆栈上分配的任何内容都具有“自动”存储期限(实际上可以将变量声明为auto,但这是默认的,如果没有指定其他存储类型,所以实际上并不真正使用关键字,但这是它的来源)
自动存储持续时间就像听起来一样,变量的持续时间是自动处理的。相反,堆上分配的任何内容都必须由您手动删除。下面是一个例子:void foo() {
bar b;
bar* b2 = new bar();}
该函数创建了三个值得考虑的值:
在第1行中,它声明了一个变量b类型bar在堆栈上(自动持续时间)。
在第2行中,它声明了bar指针b2在堆栈(自动持续时间)上,和调用新的,分配bar对象在堆中。(动态持续时间)
当函数返回时,将发生以下情况:首先,b2超出范围(破坏的顺序总是与建筑的秩序相反)。但b2只是一个指针,所以什么都没有发生,它占用的内存被释放了。更重要的是,记忆指点()bar实例)不被触及。只有指针被释放,因为只有指针具有自动持续时间。第二,b超出作用域,因此由于它具有自动持续时间,因此调用其析构函数,并释放内存。
而bar堆上的例子?可能还在那里。没有人费心把它删除,所以我们泄露了记忆。
从这个例子中,我们可以看到任何具有自动持续时间的内容都是保在超出作用域时调用其析构函数。这很有用。但是堆上分配的任何东西只要我们需要就会持续,并且可以动态调整大小,就像数组的情况一样。这也是有用的。我们可以使用它来管理内存分配。如果foo类在其构造函数中的堆上分配了一些内存,并在其析构函数中删除了该内存,怎么办?然后,我们可以获得这两个世界的优势,安全的内存分配,保证再次释放,但没有限制,迫使所有的东西在堆栈上。
这正是大多数C+代码的工作方式。看看标准图书馆的std::vector例如。通常在堆栈上分配,但可以动态调整大小。它通过必要时在堆上内部分配内存来做到这一点。类的用户从未看到这一点,因此没有可能泄漏内存,也不会忘记清理所分配的内容。
这个原则叫做Raii(资源获取是初始化),它可以扩展到任何必须获得和释放的资源。(网络套接字、文件、数据库连接、同步锁)。所有这些都可以在构造函数中获得,并在析构函数中释放,因此可以保证您获得的所有资源都将再次释放。
一般情况下,不要直接从高级代码中使用New/DELETE。始终将其包装在可以为您管理内存的类中,这将确保再次释放内存。(是的,这条规则可能有例外。特别是,智能指针要求您调用new直接将指针传递给它的构造函数,然后构造函数接管并确保delete被正确调用。但这仍然是一个非常重要的经验法则)