是的,一个在堆栈上,另一个在堆栈上。有两个重要区别:
- 首先,显而易见的,次要的是:堆分配很慢。堆栈分配很快。
- 其次,RAII更重要。由于堆栈分配的版本会自动清除,因此很有用。它的析构函数会自动调用,这使您可以保证清除由该类分配的所有资源。这是避免C ++中内存泄漏的基本方法。通过从不调用
delete
自己来避免它们,而是将其包装在delete
内部通常在其析构函数中调用的堆栈分配对象中。如果您尝试手动跟踪所有分配并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();
}
Pixel类现在在内部分配了一些堆内存,但是它的析构函数负责清理它,因此在使用该类时,我们不必担心。(我可能应该提到,为了展示一般原理,这里的最后一个示例已简化了很多。如果我们实际上要使用此类,则它也包含几个可能的错误。如果y的分配失败,则x永远不会释放,如果像素被复制,我们最终将导致两个实例都试图删除相同的数据。因此,在这里以最后的例子来说明一下。实际代码有点棘手,但它显示了一般想法)
当然,可以将相同的技术扩展到内存分配以外的其他资源。例如,它可以用来确保文件或数据库连接在使用后关闭,或释放线程代码的同步锁。