假设我定义了一些类:
class Pixel {
public:
Pixel(){ x=0; y=0;};
int x;
int y;
}
然后使用它编写一些代码。我为什么要这样做?
Pixel p;
p.x = 2;
p.y = 5;
来 self 经常写作的 Java 世界:
Pixel* p = new Pixel();
p->x = 2;
p->y = 5;
他们基本上做同样的事情,对吧? 一个在堆栈上,另一个在堆上,所以我必须稍后将其删除。两者有什么根本区别吗?为什么我应该更喜欢其中一个?
最佳答案
是的,一个在堆栈上,另一个在堆上。有两个重要的区别:
- 首先,显而易见但不太重要的一点是:堆分配速度很慢。堆栈分配很快。
- 其次,更重要的是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 永远不会被释放,如果 Pixel 被复制,我们最终会导致两个实例都试图删除相同的数据。因此,这里的最后一个例子有点不可信。实际代码有点棘手,但它显示了一般的想法)
当然,同样的技术可以扩展到内存分配以外的其他资源。例如,它可用于保证文件或数据库连接在使用后关闭,或者线程代码的同步锁被释放。
关于c++ - 为什么不在 C++ 中对所有内容都使用指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1064325/