假设我定义一些类:
class Pixel {
public:
Pixel(){ x=0; y=0;};
int x;
int y;
}
然后用它编写一些代码。 为什么我会做以下?
Pixel p;
p.x = 2;
p.y = 5;
从Java世界,我总是写来的:
Pixel* p = new Pixel();
p->x = 2;
p->y = 5;
他们基本上做同样的事情,对不对? 一个是在栈上,而另一个是在堆上,所以我不得不删除以后。 有没有两者之间有什么根本区别? 我为什么要喜欢一个比其他?
Answer 1:
是的,一个是在栈上,另一个在堆。 有两个重要的区别:
- 首先,显而易见的,并不太重要的一个:堆分配是缓慢的。 堆栈分配快。
- 其次,更重要的是RAII 。 因为栈上分配的版本被自动清除,它是有用的 。 它的析构函数自动调用,它允许你保证,由类分配的资源得到清理。 这是essentialy你如何避免在C ++内存泄漏。 您可以通过调用永远避免它们
delete
自己,而不是在调用堆栈分配的对象包装它delete
内部,typicaly在他们的析构函数。 如果您尝试手动保持所有分配的轨道,并要求delete
在正确的时间,我向你保证,你必须至少每100行代码中的内存泄漏。
作为一个小例子,考虑下面的代码:
class Pixel {
public:
Pixel(){ x=0; y=0;};
int x;
int y;
};
void foo() {
Pixel* p = new Pixel();
p->x = 2;
p->y = 5;
bar();
delete p;
}
漂亮的无辜代码,对不对? 我们创建了一个像素,那么我们调用一些不相关的操作,然后我们删除像素。 是否有内存泄漏?
答案是“有可能”。 如果会发生什么bar
抛出一个异常? delete
不会被调用,像素是永远不会被删除,而我们的内存泄漏。 现在考虑这个:
void foo() {
Pixel p;
p.x = 2;
p.y = 5;
bar();
}
这不会导致内存泄漏。 当然,在这个简单的例子,一切都在栈上,所以它被自动清除,但即使在Pixel
级做了一个动态分配的内部,那也不泄漏。 在Pixel
级将简单地因为其删除析构函数,而这个析构函数将不管我们怎么离开这个叫做foo
功能。 即使我们离开它,因为bar
引发了异常。 以下,略人为的例子示出了这样的:
class Pixel {
public:
Pixel(){ x=new int(0); y=new int(0);};
int* x;
int* y;
~Pixel() {
delete x;
delete y;
}
};
void foo() {
Pixel p;
*p.x = 2;
*p.y = 5;
bar();
}
像素类现在内部分配一些堆内存,但它的析构函数负责清理它的照顾,所以在使用该类时,我们不必担心。 (我也许应该提到的是,这里的最后一个例子是简化了很多,为了显示的一般原则。如果我们要真正使用这个类,它包含了几种可能的错误了。如果y的分配失败时,X永远不会被释放,并且如果像素被复制,我们结束了这两种情况下尝试删除相同的数据。因此,采取最后的例子在这里与一粒盐。真实世界的代码是有点棘手,但它显示的总体思路)
当然同样的技术可以被扩展到比存储器分配等资源。 例如,它可以被用来保证文件或数据库连接使用后关闭,或您的线程代码同步锁被释放。
Answer 2:
他们不是在您添加删除相同。
你举的例子是过于琐碎,但析构函数实际上包含的代码,做一些实实在在的工作。 这被称为RAII。
因此,添加删除。 确保它发生,即使异常传播。
Pixel* p = NULL; // Must do this. Otherwise new may throw and then
// you would be attempting to delete an invalid pointer.
try
{
p = new Pixel();
p->x = 2;
p->y = 5;
// Do Work
delete p;
}
catch(...)
{
delete p;
throw;
}
如果您挑选了一些更有趣的像一个文件(这是一个需要被关闭的资源)。 然后正确地做到这一点在Java中你需要做的这个指针。
File file;
try
{
file = new File("Plop");
// Do work with file.
}
finally
{
try
{
file.close(); // Make sure the file handle is closed.
// Oherwise the resource will be leaked until
// eventual Garbage collection.
}
catch(Exception e) {};// Need the extra try catch to catch and discard
// Irrelevant exceptions.
// Note it is bad practice to allow exceptions to escape a finally block.
// If they do and there is already an exception propagating you loose the
// the original exception, which probably has more relevant information
// about the problem.
}
在C相同的代码++
std::fstream file("Plop");
// Do work with file.
// Destructor automatically closes file and discards irrelevant exceptions.
虽然人们提到的速度(因为找到堆中/分配的内存)。 个人这不是一个决定因素,我(的分配器是非常快的,并已为正不断在缔造/破坏的小物件C ++使用进行了优化)。
对我来说,最主要的原因是对象的生命时间。 本地定义的对象具有一个非常具体的和明确的寿命和析构函数保证在端部被称为(并且因此可以具有特定的副作用)。 在另一方面指针控制与动态寿命的资源。
C ++和Java之间的主要区别是:
谁的概念拥有指针。 这是业主的责任,在适当的时候删除对象。 这就是为什么你很少看到这样的现实节目原始指针(因为不存在与原始指针相关联的所有权信息)。 取而代之的指针通常被包裹在智能指针。 智能指针确定谁拥有的内存语义,因此谁负责清除它。
示例如下:
std::auto_ptr<Pixel> p(new Pixel);
// An auto_ptr has move semantics.
// When you pass an auto_ptr to a method you are saying here take this. You own it.
// Delete it when you are finished. If the receiver takes ownership it usually saves
// it in another auto_ptr and the destructor does the actual dirty work of the delete.
// If the receiver does not take ownership it is usually deleted.
std::tr1::shared_ptr<Pixel> p(new Pixel); // aka boost::shared_ptr
// A shared ptr has shared ownership.
// This means it can have multiple owners each using the object simultaneously.
// As each owner finished with it the shared_ptr decrements the ref count and
// when it reaches zero the objects is destroyed.
boost::scoped_ptr<Pixel> p(new Pixel);
// Makes it act like a normal stack variable.
// Ownership is not transferable.
还有其他的。
Answer 3:
从逻辑上讲,他们做同样的事情 - 除了清理。 只要你写的示例代码,在指针情况下,内存泄漏,因为该内存不会被释放。
从Java背景的,你可能不如何C ++的多是围绕着跟踪的东西已经分配,谁负责释放它完全做好了准备。
通过使用堆栈变量在适当的时候,你不用担心释放该变量,它消失了与堆栈帧。
显然,如果你是超级细心,你总是可以在堆上分配和手动免费的,但良好的软件工程的一部分是建立东西,使得它们不能突破的方式,而不是信任你的超人类programmer-福来从不犯错。
Answer 4:
我更喜欢使用第一种方法,每当我得到,因为机会:
- 它的速度更快
- 我不必担心内存释放
- p将为整个电流范围有效的对象
Answer 5:
“为什么不使用指针在C ++的一切”
一个简单的答案 - 因为它变成一个巨大的问题管理内存 - 分配和删除/释放。
自动/栈对象去掉一些了繁忙的工作。
这只是我会说关于这个问题的第一件事。
Answer 6:
一个好的经验一般规则是永远不要使用新的,除非你绝对必须的。 你的程序会更容易维护,更不容易出错,如果你因为你不必担心在哪里清理不使用新的。
Answer 7:
代码:
Pixel p;
p.x = 2;
p.y = 5;
确实的内存没有动态分配 - 有可用内存鞋无觅处,没有内存使用情况,没有什么更新。 它是完全免费的。 堆栈在编译时变量上的编译器保留空间 - 它的作品出来有多大的空间预留,并创建一个单一的操作码移动堆栈指针所需要的量。
使用新的要求所有的内存管理开销。
接下来的问题是 - 你想要使用的堆栈空间或堆空间为您的数据。 堆(或本地)等的“p”变量不需要解除引用而使用新添加了一个间接层。
Answer 8:
是的,起初是有道理的,从Java或C#背景的。 它似乎并不像一个大交易,一定要记得释放你分配的内存。 但是当你得到你的第一个内存泄漏,你会被抓你的头,因为你发誓释放一切。 然后第二次正好与第三你会得到更加沮丧。 最后因内存问题6个月头痛后你会开始感到厌倦和堆栈分配的内存将开始看起来越来越有吸引力。 如何漂亮和干净 - 只要把它在堆栈上,而忘记了它。 很快你将使用堆栈中的任何时间,您可以摆脱它。
但是 - 有对那样的经验是无可替代的。 我的建议? 试试你的方式,现在。 你会看到的。
Answer 9:
我的直觉反应就是告诉你,这可能会导致严重的内存泄露。 其中你可能会使用指针某些情况下可能会导致混乱关于谁应该负责删除它们。 在简单的情况下,如你的榜样,这是很容易看到何时何地,你应该调用删除,但是当你开始传递类之间的指针,事情可能会变得有点困难。
我建议你寻找到升压智能指针库的指针。
Answer 10:
The best reason not to new everything is that you can very deterministic cleanup when things are on the stack. In the case of Pixel's this is not so obvious, but in the case of say a file, this becomes advantageous:
{ // block of code that uses file
File aFile("file.txt");
...
} // File destructor fires when file goes out of scope, closing the file
aFile // can't access outside of scope (compiler error)
In the case of newing a file, you would have to remember to delete it to get the same behavior. Seems like a simple issue in the above case. Consider more complex code, however, such as storing the pointers into a data structure. What if you pass that data structure to another piece of code? Who is responsible for the cleanup. Who would close all your files?
When you don't new everything, the resources are just cleaned up by the destructor when the variable goes out of scope. So you can have greater confidence that resources are successfully cleaned up.
This concept is known as RAII -- Resource Allocation Is Initialization and it can drastically improve your ability to deal with resource acquisition and disposal.
Answer 11:
第一种情况是不是总是堆栈中分配。 如果它是一个对象的一部分,它会被分配无论对象是。 例如:
class Rectangle {
Pixel top_left;
Pixel bottom_right;
}
Rectangle r1; // Pixel is allocated on the stack
Rectangle *r2 = new Rectangle(); // Pixel is allocated on the heap
堆栈变量的主要优点是:
- 您可以使用RAII模式来管理对象。 一旦对象超出范围,它的析构函数被调用。 有点像C#中的“使用”模式,但自动的。
- 有没有空引用的可能性。
- 你并不需要担心手动管理对象的内存。
- 它会导致更少的内存分配。 存储器分配,特别是小的,有可能在C至慢++比Java。
一旦对象被创建,有一个堆栈(或地方)上分配在堆上分配一个对象,之间并没有性能上的差异。
但是,除非您使用的是指针,你不能使用任何的多态的 - 对象有一个完全静态类型,它是在编译时确定。
Answer 12:
对象的生命周期。 如果你希望你的对象的生命周期将超过当前范围的生命周期,你必须使用堆。
如果在另一方面,你不需要变量超出目前的范围,声明它的堆栈。 当它超出范围时,将自动销毁。 只是要小心周围路过的地址。
Answer 13:
我会说这是一个很多关于口味的问题。 如果您创建一个界面,允许方法采取指针而不是引用,您允许呼叫者在零通过。 既然你允许用户在无传中,用户将通过在零。
既然你要问自己“会发生什么,如果这个参数是零?”,你有更多的防守代码,以null检查的照顾所有的时间。 这说明了使用参考。
但是,有时候你真的希望能够在零传递,然后引用出了问题:)指针给你更大的灵活性,让你更懒,这是非常好的。 从来没有分配,直到知道你考察调拨!
Answer 14:
这个问题是不是指针本身 (除了引入NULL
指针),但是做手工的内存管理。
有趣的部分,当然是每个Java教程中,我见过提到垃圾收集器就是这样酷辣,因为你不必记住调用delete
,在实践中C ++只需要delete
,当你调用new
(和delete[]
当你调用new[]
Answer 15:
使用指针和动态分配的对象,只有当你必须。 尽可能使用静态分配(全局或堆)对象。
- 静态对象的速度更快(没有新/删除,没有间接的方式访问它们)
- 没有对象生存发愁
- 较少的按键更可读
- 更强大。 每一个“ - >”是NIL或无效的内存潜在访问
为了在这方面明确,由“静态”,我的意思是不动态分配。 IOW,任何不在堆上。 是的,他们可以有对象的生命周期问题,太 - 在单破坏秩序的方面 - 但把它们插在堆通常不会解决任何问题。
Answer 16:
为什么不使用指针的一切?
他们是慢。
编译器优化不会与指针访问symantics有效,你可以在任何数量的网站的大约读了它,但这里有一个体面的英特尔PDF格式。
检查页,13,14,17,28,32,36;
检测循环符号不必要的内存引用:
for (i = j + 1; i <= *n; ++i) {
X(i) -= temp * AP(k); }
为循环边界的符号包含指针或存储器参考。 编译器不具有任何手段来预测是否通过指针n所引用的值被与循环迭代通过一些其它分配改变。 这将使用循环重载由N每个迭代中参考的价值。 该码发生器引擎时潜在的指针别名被发现也可以拒绝调度软件管线式环路。 由于由指针n引用的值不会在循环内anging并且它是不变的循环索引,* NS的装载至较简单的调度和指针消歧循环边界之外来进行。
......许多关于这一主题的变化....
复杂的内存引用。 或者换句话说,分析诸如复杂指针计算的引用,应变的编译器来生成高效的代码的能力。 在代码地方编译器或硬件,以便确定数据所在执行复杂的计算,应该是人们关注的焦点。 指针别名和代码简化协助识别存储器访问模式编译器,允许编译器重叠与数据操作的存储器访问。 减少不必要的内存引用可能会使编译器的能力,以流水线的软件。 许多其它的数据位置特性,诸如混叠或对准,可以如果存储器参考计算都保持简单容易地识别。 使用强度降低或感应方法来简化内存引用是协助编译器是至关重要的。
Answer 17:
看着从不同的角度的问题...
在C ++中可以参考使用指针(对象Foo *
)和参考( Foo &
)。 只要有可能,我用一个参考,而不是一个指针。 例如,通过参照本发明的功能/方法并传送的情况下,使用的引用允许代码(希望)作如下假设:
- 引用不是由函数/方法所拥有的对象,因此不应
delete
该对象。 这就像说,“在这里,用这个数据,但给它回来时,你就大功告成了。” - 空指针引用是不太可能的。 有可能被传递一个空引用,但至少不会是功能/方法的故障。 参考不能被重新分配到新的指针地址,所以您的代码不能有意外重新分配为NULL或其他一些无效的指针地址,从而导致页面错误。
Answer 18:
现在的问题是:为什么你会使用指针的一切? 堆栈分配的对象不仅是更安全,更快地创建,但有更少的打字和代码看起来更好。
Answer 19:
我还没有看到提到的某些东西是增加的内存使用情况。 假定4个字节整数和指针
Pixel p;
将使用8个字节,并且
Pixel* p = new Pixel();
将使用12个字节,增加了50%。 这听起来不像很多,直到你为512x512的图像分配足够的。 然后你说2MB,而不是3MB。 这是忽略所有这些对象上他们管理的堆的开销。
Answer 20:
Objects created on the stack are created faster than objects allocated.
Why?
Because allocating memory (with default memory manager) takes some time (to find some empty block or even allocate that block).
Also you don't have memory management problems as the stack object automatically destroys itself when out of scope.
The code is simpler when you don't use pointers. If your design allows you to use stack objects, I recommend that you do it.
I myself wouldn't complicate the problem using smart pointers.
OTOH I have worked a little in the embedded field and creating objects on the stack is not very smart (as the stack allocated for each task/thread is not very big - you must be careful).
So it's a matter of choice and restrictions, there is no response to fit them all.
And, as always don't forget to keep it simple, as much as possible.
Answer 21:
Answer 22:
这混淆了我很多,当我是一个新的C ++程序员(这是我的第一语言)。 有很多很糟糕的C ++教程,一般似乎分为两类之一:“C / C ++”教程,这实际上意味着它是一个C教程(可能与班),和C ++教程,认为C ++是与Java删除。
我觉得我花了大约1 - 1.5年(至少)键入我的代码中的“新”的任何地方。 我用STL容器,如矢量频繁,它照顾了,对我。
我想了很多的答案似乎忽略或只是避免直接说如何避免这种情况。 您一般不需要用新的分配在构造函数与析构函数中删除清理。 相反,你可以直接粘在类(而不是指向它的指针)对象本身并初始化对象本身的构造。 那么默认的构造函数,你在大多数情况下需要的一切。
对于几乎所有的情况下,这将不起作用(举例来说,如果你的风险运行的堆栈空间),你或许应该使用标准容器之一反正:的std :: string,性病::向量,和std ::地图是三个,我最常使用的,但的std ::双端队列和std ::名单也相当普遍。 其他人(之类的东西的std ::设置和非标准绳 )不使用很多,但行为类似。 他们都从自由存储区(C ++的说法在其他一些语言的“堆”)分配,请参见: C ++ STL问题:分配器
Answer 23:
第一种情况是最好的,除非有更多的成员加入到像素级。 随着越来越多的成员被添加,存在堆栈溢出异常的可能性
文章来源: Why not use pointers for everything in C++?