我已经使用C ++了很短的时间,并且一直想知道new关键字。简而言之,我应该使用它吗?

1)使用新关键字...

MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";


2)没有新关键字关键字...

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.


这有助于防止内存泄漏,因为您始终必须将删除项放在某个位置(例如,剪切并粘贴时)到析构函数或其他方法。)

评论

简短的答案是,当您可以摆脱它时,请使用简短的版本。 :)

与始终编写相应的删除方法相比,一种更好的技术-使用STL容器和智能指针,如std :: vector和std :: shared_ptr。这些为您包装了对new和delete的调用,因此您泄漏内存的可能性更低。问问自己:例如,您是否始终记得在可能引发异常的地方都放置相应的删除?手动进行删除比您想象的要难。

@nbolton回复:更新1-C ++的美丽之处之一是它允许您将用户定义类型存储在堆栈中,而C#之类的垃圾收集lang则迫使您将数据存储在堆中。在堆上存储数据比在堆栈上存储数据要消耗更多的资源,因此,除了UDT需要大量内存来存储其数据之外,您应该更喜欢堆栈而不是堆。 (这也意味着默认情况下按值传递对象)。一个更好的解决方案是将数组通过引用传递给函数。

#1 楼

方法1(使用new


为免费存储中的对象分配内存(这通常与堆相同)
要求您稍后显式地对对象进行delete 。 (如果不删除它,可能会造成内存泄漏)。
内存会一直分配,直到您对其进行分配。 (即您可以delete使用return创建的对象)
问题中的示例将泄漏内存,除非指针为new d;

方法2(不使用delete


分配内存,并且无论使用哪个控制路径,或者是否引发异常,都应始终将其删除。对于堆栈上的对象(所有局部变量都存放在此的对象),通常堆栈可用的内存较少;如果分配过多的对象,则有栈溢出的风险。
以后就不需要new
超出范围时不再分配内存。 (即,您不应该delete指向堆栈上一个对象的指针)

关于要使用哪个对象;鉴于上述限制,您选择最适合您的方法。

一些简单的情况:


如果您不想担心调用return(以及可能导致内存泄漏的问题),则不应使用delete
如果要从函数返回指向对象的指针,则必须使用new



评论


一个nitpick -我相信new运算符从“免费存储”中分配内存,而malloc从“堆”中分配。尽管实际上它们通常是相同的,但不能保证它们是同一件事。参见gotw.ca/gotw/009.htm。

–弗雷德·拉森(Fred Larson)
09年3月17日在18:46

我认为您的答案可能会更清楚。 (99%的时间,选择很简单。在包装器对象上使用方法2,该对象在构造函数/析构函数中调用new / delete)

–杰夫
09年3月17日在23:27

@jalf:方法2是不使用新方法的方法:-/在任何情况下,使用方法2(很多情况下您使用的代码都不是新方法)在很多情况下,您编写的代码都会简单得多(例如,处理错误情况)

–丹尼尔·勒凯曼(Daniel LeCheminant)
09年3月18日在1:04

另一个挑剔...您应该更加清楚,即使面对异常,尼克的第一个示例也会泄漏内存,而第二个示例不会泄漏内存。

– Arafangion
09年3月24日在1:52

@ Fred,Arafangion:感谢您的见解;我已将您的评论纳入答案。

–丹尼尔·勒凯曼(Daniel LeCheminant)
2009年3月24日6:13

#2 楼

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

没有分配给new的所有对象的行为都非常类似于C#中的值类型(而且人们经常说这些对象是分配在堆栈上的,这可能是最常见的/更确切地说,不使用new分配的对象具有自动存储持续时间
new分配的所有对象都分配在堆上,并返回指向它的指针,就像C#中的引用类型一样。 br />
分配给堆栈的任何内容都必须具有恒定大小,该大小在编译时确定(编译器必须正确设置堆栈指针,否则,如果对象是另一个类的成员,则必须进行调整这就是为什么C#中的数组是引用类型的原因。它们必须是引用类型,因为有了引用类型,我们就可以在运行时决定需要多少内存。在这里也是如此。仅常量大小的数组(可以在编译时确定的大小-时间)可以分配自动存储时间(在堆栈上)。必须通过调用new在堆上分配动态大小的数组。

(这就是与C#相似之处停止的地方)

现在,在堆栈上分配的任何内容都具有“自动”存储持续时间(实际上您可以将变量声明为auto,但是如果未指定其他存储类型,则这是默认设置,因此实际上并没有使用该关键字,但这是它的来源)

自动存储持续时间意味着确切地说,变量的持续时间会自动处理。相比之下,堆上分配的所有内容都必须由您手动删除。
下面是一个示例:

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


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

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

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

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

以及堆上的bar实例?它可能仍然存在。没有人愿意删除它,因此我们泄漏了内存。从这个示例中,我们可以看到,任何具有自动持续时间的东西都可以保证在超出范围时调用其析构函数。这很有用。但是在堆上分配的任何东西都可以持续到我们需要的时间,并且可以动态调整大小,例如数组。这也很有用。我们可以使用它来管理我们的内存分配。如果Foo类在其构造函数中的堆上分配了一些内存,并在其析构函数中删除了该内存,该怎么办。这样我们就可以兼得两全其美,可以保证再次释放安全的内存分配,但没有将所有内容都强制放入堆栈的限制。

C ++代码有效。
以标准库的std::vector为例。它通常在堆栈上分配,但可以动态调整大小和调整大小。并且它通过在必要时在堆上内部分配内存来做到这一点。该类的用户从未看到过此消息,因此没有机会泄漏内存或忘记清理分配的内容。

该原理称为RAII(资源获取是初始化),并且可以扩展到必须获取和释放的任何资源。 (网络套接字,文件,数据库连接,同步锁)。所有这些都可以在构造函数中获取,然后在析构函数中释放,因此可以确保您获取的所有资源都会再次释放。

作为一般规则,切勿直接使用new / delete从您的高级代码。始终将其包装在可以为您管理内存的类中,这将确保再次释放该内存。 (是的,此规则可能会有例外。特别是,智能指针要求您直接调用new,并将该指针传递给其构造函数,然后该构造函数接管并确保正确调用delete。但这仍然是非常重要的规则的拇指)

评论


“没有分配给new的所有东西都放在堆栈上”不在我使用的系统中……通常将初始化(和取消初始化)的全局(静态)数据放在自己的段中。例如,.data,.bss等...链接器段。 Pedantic,我知道...

–丹
09年3月17日在20:51

当然,您是对的。我不是真的在考虑静态数据。我的坏,当然。 :)

–杰夫
09年3月17日在21:06

为什么在堆栈上分配的任何内容都必须具有恒定的大小?

–user541686
2012-2-24在21:59

并非总是如此,有几种方法可以规避它,但通常情况下可以,因为它在堆栈上。如果它位于堆栈的顶部,则可能可以调整其大小,但是一旦将其他内容推入堆栈的顶部,它就会被“围墙”,两侧被对象包围,因此无法真正调整其大小。是的,说它总是必须有一个固定的大小有点简化,但是它传达了基本思想(我不建议您弄乱C函数,因为它会使您在堆栈分配方面太有创意了)

–杰夫
13年2月15日在22:41



#3 楼


我应该使用哪种方法?


这几乎永远不会取决于您的键入首选项,而是取决于上下文。如果需要将对象保留在几个堆栈中,或者如果该对象对于堆栈而言太重,则可以在免费存储区中分配它。同样,由于您正在分配对象,因此您还负责释放内存。查找delete运算符。

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

#4 楼

简短的答案是:如果您是C ++的初学者,则永远不要自己使用newdelete。相反,您应该使用智能指针,例如std::unique_ptrstd::make_unique(或更不常见的是std::shared_ptr)和std::make_shared)。这样,您不必担心内存泄漏。而且,即使您更高级,最佳实践通常还是将使用newdelete的自定义方式封装到专门用于对象生命周期问题的小类(例如自定义智能指针)中。 >
当然,在后台,这些智能指针仍在执行动态分配和释放,因此使用它们的代码仍将具有关联的运行时开销。这里的其他答案涵盖了这些问题,以及如何在何时使用智能指针而不是仅在堆栈上创建对象或将它们合并为对象的直接成员方面做出设计决策,我将不再赘述。但我的执行摘要将是:在某些事情迫使您这样做之前,不要使用智能指针或动态分配。

评论


有趣的是,随着时间的流逝,答案可能会如何变化;)

–狼
18年4月6日在13:47

#5 楼

如果您使用C ++编写,则可能是为了提高性能。使用new和免费存储比使用堆栈要慢得多(尤其是在使用线程时),因此仅在需要时才使用它。

正如其他人所说,当对象需要使用时,您需要新的存储在函数或对象范围之外,对象确实很大,或者在编译时不知道数组的大小。

此外,请尽量避免使用delete。而是将新包装为智能指针。让智能指针调用为您删除。

在某些情况下,智能指针并不聪明。切勿将std :: auto_ptr <>存储在STL容器中。由于容器内的复制操作,它将过早删除指针。另一种情况是当您有一个非常大的指向对象的STL指针容器。 boost :: shared_ptr <>会增加大量的速度开销,因为它会增加和减少引用计数。在这种情况下,更好的方法是将STL容器放入另一个对象,并为该对象提供一个析构函数,该析构函数将对容器中的每个指针调用delete。

#6 楼

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

#7 楼

简单的答案是肯定的-new()在堆上创建一个对象(不幸的副作用是,您必须管理其生存期(通过显式调用delete),而第二种形式则在当前堆栈中创建一个对象范围,并且该对象超出范围将被销毁。

#8 楼

如果仅在单个函数的上下文中使用变量,则最好使用堆栈变量,即选项2。正如其他人所述,您不必管理堆栈变量的生存期-它们是构造的,自动销毁。而且,相比之下,在堆上分配/取消分配变量的速度很慢。如果经常调用函数,那么使用堆栈变量与堆变量相比,您将看到性能上的显着提高。

那有两个明显的实例,其中堆栈变量不足。

如果堆栈变量具有较大的内存占用空间,则存在堆栈溢出的风险。默认情况下,在Windows上,每个线程的堆栈大小为1 MB。您不太可能创建大小为1 MB的堆栈变量,但必须记住,堆栈利用率是累积的。如果您的函数调用的函数调用了另一个函数的调用,另一个函数又调用了...,则所有这些函数中的堆栈变量将占用同一堆栈上的空间。递归函数可以快速遇到此问题,具体取决于递归的深度。如果出现问题,则可以增加堆栈的大小(不建议使用),或者使用new运算符(建议)在堆上分配变量。

另一种可能的情况是,您的变量需要“有效”超出功能范围。在这种情况下,您将在堆上分配变量,以便可以在任何给定函数的范围之外访问它。

#9 楼

您是将myClass从函数中传递出来,还是期望它存在于该函数之外?就像其他人所说的,当您不在堆上进行分配时,这与范围有关。当您离开该功能时,该功能最终将消失。初学者犯的经典错误之一是尝试在函数中创建某个类的本地对象,然后将其返回而不在堆上分配该对象。我记得在早期用c ++调试过这种东西。

#10 楼

第二种方法在堆栈上创建实例,并声明诸如int之类的内容以及传递到函数中的参数列表。

第一种方法为堆栈上的指针留出空间,您已将其设置为在堆或免费存储区上已分配新MyClass的内存中的位置。

第一种方法还要求delete使用new创建的内容,而在第二种方法,当类超出范围时(通常是下一个大括号),该类将自动销毁并释放。

#11 楼

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