我应该什么时候在C ++中使用新的关键字?
我一直在使用C ++,我一直在想新的关键字。 简单地说,我应该使用它,还是不?
1)使用新的关键字…
MyClass* myClass = new MyClass(); myClass->MyField = "Hello world!";
2)没有新的关键字…
MyClass myClass; myClass.MyField = "Hello world!";
从实现的角度来看,他们似乎没有什么不同(但我确定他们是)…但是,我的主要语言是C#,当然第一种方法是我习惯的。
难度似乎是方法1更难与std C ++类一起使用。
我应该使用哪种方法?
更新1:
我最近使用新的关键字堆内存(或自由存储 )为超出范围的大数组(即从函数返回)。 在我使用堆栈之前,导致一半的元素在范围之外被破坏,切换堆使用确保了元素的完整性。 好极了!
更新2:
我的一个朋友最近告诉我,使用new
关键字有一个简单的规则。 每次键入new
,输入delete
。
Foobar *foobar = new Foobar(); delete foobar; // TODO: Move this to the right place.
这有助于防止内存泄漏,因为你总是必须把删除的地方(即当你剪切和粘贴到一个析构函数或其他)。
方法1(使用new
)
- 为空闲存储上的对象分配内存(这通常与堆相同)
- 要求你以后明确地
delete
你的对象。 (如果你不删除它,你可以创建一个内存泄漏) - 内存保持分配,直到您
delete
它。 (即你可以return
一个你用new
创建的对象) - 在问题中的例子将泄漏内存,除非指针是
delete
d; 并且应该始终将其删除 ,而不管采取哪种控制路径,或者抛出异常。
方法2(不使用new
)
- 为堆栈上的对象分配内存(所有局部变量都去)通常有更少的内存可用于堆栈; 如果分配过多的对象,则可能会导致堆栈溢出。
- 您将不需要稍后
delete
它。 - 当超出范围时,内存不再被分配。 (即你不应该
return
一个指向栈上的对象的指针)
至于使用哪一个; 在上面的限制条件下,你选择最适合你的方法。
一些简单的情况:
- 如果你不想担心调用
delete
,(和潜在的内存泄漏 ),你不应该使用new
。 - 如果你想从一个函数返回一个指向你的对象的指针,你必须使用
new
两者之间有一个重要的区别。
没有用new
分配的东西在C#中的表现与值类型非常类似(人们经常说这些对象是在堆栈中分配的,这可能是最常见/最明显的情况,但并非总是如此。 自动存储时间在堆中分配了所有分配的new
元素,并返回一个指针,与C#中的引用类型完全一样。
在堆栈上分配的任何东西都必须具有一个常量大小,在编译时确定(编译器必须正确设置堆栈指针,或者如果该对象是另一个类的成员,则必须调整其他类的大小) 。 这就是为什么C#中的数组是引用类型。 它们必须是,因为有了引用类型,我们可以在运行时决定需要多少内存。 这里同样适用。 只有具有固定大小的数组(可以在编译时确定的大小)才能被分配自动存储时间(在堆栈上)。 动态大小的数组必须通过调用new
来分配在堆上。
(这就是C#停止的地方)
现在,在堆栈上分配的任何东西都具有“自动”存储持续时间(实际上可以将一个变量声明为auto
,但是如果没有指定其他存储类型,则这是缺省的,所以关键字在实际中并不真正使用,它来自)
自动存储时间意味着它听起来像什么,变量的持续时间自动处理。 相比之下,任何分配在堆上的东西都必须由您手动删除。 这是一个例子:
void foo() { bar b; bar* b2 = new bar(); }
这个函数创建了三个值得考虑的值:
在第一行,它在堆栈中声明了一个类型bar
的变量b
(自动持续时间)。
在第二行,它声明栈上的bar
指针b2
(自动持续时间), 并调用new,在堆上分配一个bar
对象。 (动态持续时间)
当函数返回时,会发生以下情况:首先, b2
超出范围(销毁顺序总是与构造顺序相反)。 但是b2
只是一个指针,所以什么都不会发生,它所占用的内存就被释放了。 而且重要的是,它指向的内存(堆上的bar
实例)不会被触摸。 只有指针被释放,因为只有指针有自动持续时间。 其次, b
超出范围,因此它具有自动持续时间,调用析构函数,释放内存。
和堆上的实例吗? 它可能还在那里。 没人打扰删除它,所以我们泄漏了内存。
从这个例子中,我们可以看到,具有自动持续时间的任何东西都保证在析构函数超出作用域时被调用。 这很有用。 但是,只要我们需要在堆上分配任何东西,就可以动态调整大小,就像在数组中一样。 这也是有用的。 我们可以使用它来管理我们的内存分配。 如果Foo类在其构造函数的堆中分配了一些内存,并在其析构函数中删除了该内存,该怎么办? 然后,我们可以得到两全其美的好处,安全的内存分配,保证可以再次释放,但没有强制所有东西在堆栈上的限制。
这几乎是大多数C ++代码的工作原理。 看看标准库的std::vector
例如。 通常分配在堆栈上,但可以动态调整大小和调整大小。 这是通过在内部根据需要分配内存来实现的。 这个班级的用户从来没有看到这个,所以没有机会泄漏记忆,或忘记清理你分配的内容。
这个原则被称为RAII(资源获取是初始化),它可以扩展到任何必须获得和释放的资源。 (网络套接字,文件,数据库连接,同步锁)。 所有这些都可以在构造函数中获得,并在析构函数中释放,这样可以保证您获得的所有资源都将被释放。
通常,不要直接从高级代码中使用new / delete。 总是将它包装在一个可以为你管理内存的类中,并且可以确保它被再次释放。 (是的,这个规则可能有例外,特别是智能指针需要你直接调用new
,然后把指针传递给它的构造函数,然后它继承并保证delete
的调用是正确的,但这仍然是一个非常重要的规则拇指)
我应该使用哪种方法?
这几乎从来不会由您的打字偏好,而是由上下文决定。 如果您需要将对象保留在几个堆栈中,或者如果堆栈太重,则将其分配到免费商店中。 另外,由于您正在分配一个对象,因此您还负责释放内存。 查找delete
操作符。
为了减轻使用免费店管理的负担,人们发明了像auto_ptr
和unique_ptr
这样的东西。 我强烈建议你看看这些。 他们甚至可以帮助你的打字问题;-)
如果你用C ++编写,你可能会写性能。 使用新的和免费的商店比使用堆栈慢得多(特别是在使用线程时),所以只有在需要时才使用它。
正如其他人所说的,当对象需要在函数或对象作用域之外时,需要新建对象,或者在编译时不知道数组的大小。
另外,尽量避免使用删除。 相反,把你的新包装成一个智能指针。 让智能指针为你删除。
在某些情况下,智能指针不够智能。 切勿将std :: auto_ptr <>存储在STL容器中。 由于容器内的复制操作,它会过早地删除指针。 另一种情况是当你有一个非常大的指向对象的STL容器时。 boost :: shared_ptr <>将会产生大量的速度开销,因为它会使引用计数上下颠簸。 在这种情况下,更好的方法是将STL容器放入另一个对象中,并为该对象提供一个析构函数,该函数将调用容器中每个指针的delete。
没有new
关键字,你正在存储的调用堆栈 。 在堆栈上存储过大的变量会导致堆栈溢出 。
如果你的变量只在一个函数的上下文中使用,你最好使用一个栈变量,即选项2.正如其他人所说的,你不必管理栈变量的生命周期 – 它们是构造的自动破坏。 而且,通过比较来分配/取消分配堆上的变量是很慢的。 如果你的函数经常被调用,如果使用栈变量和堆变量,你会看到巨大的性能提升。
也就是说,有几个显而易见的情况,堆栈变量不足。
如果堆栈变量具有较大的内存占用量,那么存在溢出堆栈的风险。 默认情况下,Windows 上每个线程的堆栈大小为1 MB 。 您不太可能创建大小为1 MB的堆栈变量,但是您必须记住堆栈利用率是累积的。 如果你的函数调用一个调用另一个调用另一个函数的函数…,那么所有这些函数中的堆栈变量将占用同一个堆栈上的空间。 递归函数可能很快就会遇到这个问题,具体取决于递归的深度。 如果这是一个问题,您可以增加堆栈的大小(不推荐)或使用新的操作符(建议)在堆上分配变量。
另一种更可能的情况是你的变量需要超出你的功能范围。 在这种情况下,您将在堆上分配变量,以便可以在任何给定函数的范围之外到达该变量。
你是否将myClass从一个函数中传出,或者期望它在这个函数之外存在? 正如其他一些人所说的那样,当你没有在堆上分配时,所有的范围都是关于范围的。 当你离开这个功能时,它会消失(最终)。 初学者犯的经典错误之一就是尝试在函数中创建一个类的本地对象,并返回它,而不是将其分配在堆上。 我可以记得在我早期的c ++中调试这种事情。
简单的答案是肯定的 – new()在堆上创建一个对象(不幸的副作用,你必须管理它的生命周期(通过显式调用delete),而第二种形式在当前堆栈中创建一个对象范围,那个对象在超出范围时会被销毁。
简短的回答是,“新”关键字是非常重要的,因为当你使用它时,对象数据存储在堆中,而不是堆栈,这是最重要的!
第二种方法在堆栈上创建实例,以及诸如声明int
东西以及传递给函数的参数列表。
第一种方法为堆栈上的指针留出空间,这个指针已经被设置到内存中已经在堆上分配了新的MyClass
的地方,或者是免费的存储区。
第一种方法也要求你用new
delete
你创建的东西,而在第二种方法中,当它超出了作用域(通常是下一个右括号)时,这个类会被自动析构和释放。