问题描述
假设我定义了一些类:
class Pixel {
public:
Pixel(){ x=0; y=0;};
int x;
int y;
}
然后使用它编写一些代码.我为什么要执行以下操作?
Then write some code using it. Why would I do the following?
Pixel p;
p.x = 2;
p.y = 5;
来自 Java 世界,我一直在写:
Coming from a Java world I always write:
Pixel* p = new Pixel();
p->x = 2;
p->y = 5;
他们基本上做同样的事情,对吧?一个在堆栈上,而另一个在堆上,所以我稍后必须删除它.两者之间有什么根本区别吗?为什么我应该更喜欢一个?
They basically do the same thing, right?One is on the stack while the other is on the heap, so I'll have to delete it later on. Is there any fundamental difference between the two? Why should I prefer one over the other?
推荐答案
是的,一个在堆栈上,另一个在堆上.有两个重要区别:
Yes, one is on the stack, the other on the heap. There are two important differences:
- 首先,显而易见但不太重要的一点是:堆分配很慢.堆栈分配很快.
- 第二,更重要的是RAII.因为堆栈分配的版本会自动清理,所以很有用.它的析构函数会被自动调用,这使您可以保证该类分配的任何资源都得到清理.这基本上是您在 C++ 中避免内存泄漏的方法.您可以通过从不自己调用
delete
来避免它们,而是将其包装在堆栈分配的对象中,这些对象在内部调用delete
,通常在它们的析构函数中.如果您尝试手动跟踪所有分配,并在正确的时间调用delete
,我向您保证每 100 行代码至少会发生内存泄漏.
- First, the obvious, and less important one: Heap allocations are slow. Stack allocations are fast.
- Second, and much more important is RAII. Because the stack-allocated version is automatically cleaned up, it is useful. Its destructor is automatically called, which allows you to guarantee that any resources allocated by the class get cleaned up. This is essentialy how you avoid memory leaks in C++. You avoid them by never calling
delete
yourself, instead wrapping it in stack-allocated objects which calldelete
internally, typicaly in their destructor. If you attempt to manually keep track of all allocations, and calldelete
at the right times, I guarantee you that you'll have at least a memory leak per 100 lines of code.
作为一个小例子,考虑以下代码:
As a small example, consider this code:
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;
}
很无辜的代码,对吧?我们创建一个像素,然后调用一些不相关的函数,然后删除该像素.是否存在内存泄漏?
Pretty innocent code, right? We create a pixel, then we call some unrelated function, and then we delete the pixel. Is there a memory leak?
答案是可能".如果 bar
抛出异常会发生什么?delete
永远不会被调用,像素永远不会被删除,我们会泄漏内存.现在考虑这个:
And the answer is "possibly". What happens if bar
throws an exception? delete
never gets called, the pixel is never deleted, and we leak memory. Now consider this:
void foo() {
Pixel p;
p.x = 2;
p.y = 5;
bar();
}
这不会泄漏内存.当然,在这个简单的例子中,一切都在堆栈上,所以它会自动清理,但即使 Pixel
类在内部进行了动态分配,也不会泄漏.Pixel
类将简单地被赋予一个析构函数来删除它,无论我们如何离开 foo
函数,都会调用这个析构函数.即使我们因为 bar
抛出异常而离开它.以下稍微做作的示例显示了这一点:
This won't leak memory. Of course in this simple case, everything is on the stack, so it gets cleaned up automatically, but even if the Pixel
class had made a dynamic allocation internally, that wouldn't leak either. The Pixel
class would simply be given a destructor that deletes it, and this destructor would be called no matter how we leave the foo
function. Even if we leave it because bar
threw an exception. The following, slightly contrived example shows this:
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 被复制,我们最终会导致两个实例都试图删除相同的数据.因此,请保留最后一个示例.实际代码有点棘手,但它显示了总体思路)
The Pixel class now internally allocates some heap memory, but its destructor takes care of cleaning it up, so when using the class, we don't have to worry about it. (I should probably mention that the last example here is simplified a lot, in order to show the general principle. If we were to actually use this class, it contains several possible errors too. If the allocation of y fails, x never gets freed, and if the Pixel gets copied, we end up with both instances trying to delete the same data. So take the final example here with a grain of salt. Real-world code is a bit trickier, but it shows the general idea)
当然,相同的技术可以扩展到内存分配以外的其他资源.例如,它可用于保证文件或数据库连接在使用后关闭,或者释放线程代码的同步锁.
Of course the same technique can be extended to other resources than memory allocations. For example it can be used to guarantee that files or database connections are closed after use, or that synchronization locks for your threading code are released.
这篇关于为什么不在 C++ 中对所有内容都使用指针?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!