在c++中什么时候应该使用new关键字?

我已经使用c++一段时间了,我一直在想new关键字。简单地说,我是否应该使用它?

  1. 使用new关键字…
    MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";
  1. 没有new关键字…
    MyClass myClass;
myClass.MyField = "Hello world!";

从实现的角度来看,它们似乎并没有什么不同(但我确信它们确实不同)……然而,我的主要语言是c#,当然第一个方法是我所习惯的。

困难在于方法1很难与std c++类一起使用。

我应该用哪种方法?

更新1:

我最近使用new关键字用于内存(或免费存储),用于一个超出作用域的大数组(即从函数返回)。在我使用堆栈之前,会导致一半的元素在作用域外损坏,切换到堆使用可以确保元素完好无损。耶!

更新2:

我的一个朋友最近告诉我,使用new关键字有一个简单的规则;每次输入new时,输入delete

    Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.

这有助于防止内存泄漏,因为您总是必须将删除放在某个地方(即当您剪切并粘贴到析构函数或其他方法时)。

163421 次浏览

简短的回答是肯定的,“new”关键字非常重要,因为当你使用它时,对象数据存储在堆上,而不是堆栈上,这是最重要的!

简单的答案是肯定的——new()在堆上创建一个对象(不幸的副作用是,您必须管理它的生命周期(通过显式地对它调用delete),而第二种形式在当前作用域的堆栈中创建一个对象,当它超出作用域时,该对象将被销毁。

方法1(使用new)

  • < >强自由存储< / >强上的对象分配内存(这通常与相同)
  • 要求你以后显式delete你的对象。(如果你不删除它,你可能会创建一个内存泄漏)
  • 内存一直分配,直到你delete它。(即你可以return一个你用new创建的对象)
  • 问题中的例子将内存泄漏,除非指针是deleted;和应该被删除,不管采用哪个控制路径,或者是否抛出异常。

方法2(未使用new)

  • 堆栈上的对象分配内存(所有局部变量都在这里)。如果分配的对象太多,就有堆栈溢出的风险。
  • 你以后不需要delete它。
  • 当内存超出作用域时,不再分配内存。(例如,你不应该return指向堆栈中的对象)

至于用哪一个;考虑到上述约束条件,您可以选择最适合自己的方法。

一些简单的例子:

  • 如果你不想担心调用delete(以及可能导致内存泄漏),你就不应该使用new
  • 如果你想从函数返回指向对象的指针,你必须使用new

第二个方法在堆栈上创建实例,以及声明为int的东西和传递给函数的参数列表。

第一个方法为堆栈上的指针腾出空间,你已经将其设置为内存中的位置,在这个位置上,新的MyClass已经在堆或自由存储区上分配了。

第一种方法还要求你delete你用new创建的东西,而在第二种方法中,类在超出作用域(通常是下一个右括号)时自动销毁并释放。

如果没有new关键字,则将其存储在调用堆栈上。在堆栈中存储过大的变量将导致堆栈溢出

我应该用哪种方法?

这几乎从来不是由您的输入首选项决定的,而是由上下文决定的。如果你需要将对象保存在几个堆栈中,或者它对于堆栈来说太重了,你可以将它分配到免费存储中。此外,由于您正在分配一个对象,您还负责释放内存。查找delete操作符。

为了减轻使用免费商店管理的负担,人们发明了auto_ptrunique_ptr之类的东西。我强烈建议你看看这些。它们甚至可能对你的打字问题有帮助;-)

你是将myClass传递给函数,还是期望它存在于函数之外?正如其他人所说,当您不在堆上分配时,这完全是关于范围的问题。当你离开函数时,它(最终)消失了。初学者常犯的一个经典错误是试图在函数中创建某个类的局部对象,并返回它而不将其分配到堆上。我还记得在我早期使用c++时调试这类事情。

如果变量只在单个函数的上下文中使用,那么最好使用堆栈变量,即选项2。正如其他人所说,您不必管理堆栈变量的生命周期——它们是自动构造和销毁的。而且,相比之下,在堆上分配/释放变量的速度较慢。如果函数被足够频繁地调用,使用堆栈变量而不是堆变量,您将看到巨大的性能改进。

也就是说,在一些明显的实例中,堆栈变量是不够的。

如果堆栈变量的内存占用很大,那么就会有堆栈溢出的风险。默认情况下,Windows上为每个线程的堆栈大小为1mb。不太可能创建大小为1mb的堆栈变量,但必须记住,堆栈利用率是累积的。如果你的函数调用了一个函数,这个函数调用了另一个函数,而另一个函数又调用了另一个函数。,所有这些函数中的堆栈变量都在同一个堆栈上占用空间。递归函数很快就会遇到这个问题,这取决于递归的深度。如果这是一个问题,您可以增加堆栈的大小(不推荐)或使用new操作符在堆上分配变量(推荐)。

另一种更可能的情况是,您的变量需要“生存”在函数的作用域之外。在这种情况下,可以在堆上分配变量,以便在任何给定函数的作用域之外都可以访问它。

这两者之间有一个重要的区别。

所有没有使用new分配的对象都表现得很像c#中的值类型(人们经常说这些对象分配在堆栈上,这可能是最常见/最明显的情况,但并不总是正确的)。更准确地说,不使用new分配的对象具有的自动存储持续时间new分配的所有东西都在堆上分配,并返回指向它的指针,就像c#中的引用类型一样

在堆栈上分配的任何东西都必须有一个常量大小,在编译时确定(编译器必须正确设置堆栈指针,或者如果对象是另一个类的成员,它必须调整另一个类的大小)。这就是c#中的数组是引用类型的原因。它们必须是,因为使用引用类型,我们可以在运行时决定需要多少内存。这里也一样。只有具有常量大小(在编译时可以确定的大小)的数组才能被分配自动存储持续时间(在堆栈上)。必须通过调用new在堆上分配动态大小的数组。

(这就是与c#的相似之处)

现在,在堆栈上分配的任何东西都有&;automatic&;存储持续时间(实际上可以将变量声明为auto,但如果没有指定其他存储类型,则这是默认值,因此在实践中并不真正使用关键字,但这就是它的来源)

自动存储持续时间的意思就像它听起来一样,变量的持续时间是自动处理的。相比之下,在堆上分配的任何内容都必须由您手动删除。 下面是一个例子:

void foo() {
bar b;
bar* b2 = new bar();
}

这个函数创建了三个值得考虑的值:

在第1行,它在堆栈上声明了一个类型为bar的变量b(自动持续时间)。

在第2行,它在堆栈上声明了一个bar指针b2(自动持续时间),而且调用new,在堆上分配一个bar对象。(动态时间)

当函数返回时,将发生以下情况: 首先,b2超出范围(破坏的顺序总是与构造的顺序相反)。但是b2只是一个指针,所以什么都不会发生,它占用的内存会被释放。重要的是,它指出(堆上的bar实例)的内存不会被触及。只有指针被释放,因为只有指针具有自动持续时间。 其次,b超出作用域,因此由于它具有自动持续时间,因此将调用它的析构函数,并释放内存

和堆上的barinstance ?它可能还在那里。没人删除它,所以我们泄露了内存。

从这个例子中,我们可以看到任何具有自动持续时间的对象都是保证,以便在超出作用域时调用其析构函数。这是有用的。但是在堆上分配的任何东西都可以持续到我们需要它的时候,并且可以动态地调整大小,就像数组的情况一样。这也很有用。我们可以用它来管理内存分配。如果Foo类在其构造函数中在堆上分配了一些内存,并在其析构函数中删除了这些内存。这样我们就可以两全齐美,安全的内存分配保证再次被释放,但没有强迫所有东西都在堆栈上的限制。

这几乎就是大多数c++代码的工作方式。 例如,看看标准库的std::vector。它通常在堆栈上分配,但可以动态地调整大小和大小。它通过在必要时在堆上内部分配内存来实现这一点。类的用户永远不会看到这个,所以不会有泄漏内存的机会,也不会忘记清理分配的内存

这个原则被称为RAII(资源获取即初始化),它可以扩展到任何必须获取和释放的资源。(网络套接字,文件,数据库连接,同步锁)。所有这些资源都可以在构造函数中获取,并在析构函数中释放,因此可以保证获得的所有资源都将再次被释放。

作为一般规则,永远不要直接从高级代码中使用new/delete。始终将它包装在一个可以为您管理内存的类中,并确保它再次被释放。(是的,这条规则可能有例外。特别是,智能指针要求你直接调用new,并将指针传递给它的构造函数,然后由构造函数接管并确保正确调用delete。但这仍然是一个非常重要的经验法则)

如果你是用c++写的,你可能是为了性能而写的。使用new和free store要比使用堆栈慢得多(特别是在使用线程时),所以只在需要时使用它。

正如其他人所说,当你的对象需要存在于函数或对象作用域之外,对象非常大,或者当你在编译时不知道数组的大小时,你需要new。

另外,尽量避免使用delete。把你的新代码包装成智能指针。让智能指针为你调用删除。

在某些情况下,智能指针并不智能。不要将std::auto_ptr<>存储在STL容器中。它会因为容器内的复制操作而过早地删除指针。另一种情况是当你有一个非常大的STL容器,里面是指向对象的指针。Boost::shared_ptr<>将有大量的速度开销,因为它将引用计数上下颠簸。在这种情况下,更好的方法是将STL容器放入另一个对象中,并给该对象一个析构函数,该析构函数将对容器中的每个指针调用delete。

简单的回答是:如果你是c++的初学者,你应该自己使用newdelete

相反,你应该使用智能指针,如std::unique_ptrstd::make_unique(或较少的情况下,std::shared_ptrstd::make_shared)。这样,您就不必太担心内存泄漏了。即使你更高级,最好的做法通常是将你使用newdelete的自定义方式封装到一个专门用于对象生命周期问题的小类(例如自定义智能指针)中。

当然,在幕后,这些智能指针仍然在执行动态分配和回收,因此使用它们的代码仍然会有相关的运行时开销。这里的其他答案涵盖了这些问题,以及如何在何时使用智能指针而不是仅仅在堆栈上创建对象或将它们作为对象的直接成员进行设计决策,我将不再重复这些问题。但我的执行总结是:不要使用智能指针或动态分配,除非有什么迫使你这样做。

c++核心指南R.11:避免显式使用newdelete

自从这个问题的答案被写出来以来,情况已经发生了显著的变化。具体来说,c++已经发展成为一门语言,标准库现在更加丰富。为什么这很重要?因为两个因素的结合:

  • 使用newdelete有潜在的危险:如果你没有在不再使用时保持一个非常强大的规则delete'ing你分配的所有东西,内存可能会泄漏;并且永远不会deleteing当前未分配的内容。
  • 标准库现在提供了智能指针,它封装了newdelete调用,这样你就不必自己管理免费存储/堆上的分配。标准库和其他地方的其他容器也是如此。

这已经发展成为c++社区的“核心指导方针”之一。以编写更好的c++代码,如链接文档所示。当然,这个规则也有例外:有人需要编写那些使用newdelete的封装类;但那个人很少是你自己。

补充@DanielSchepler的有效答案: