Так, один на стосі, інший на купі. Є дві важливі відмінності:
- По-перше, очевидний і менш важливий: розподіл купи відбувається повільно. Розподіл стеків відбувається швидко.
- По-друге, і набагато важливішим є 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 копіюється, ми в кінцевому підсумку обидва екземпляри намагаємось видалити однакові дані. Тож візьміть остаточний приклад тут із невеликою кількістю солі. Реальний код трохи складніший, але він показує загальну ідею)
Звичайно, та сама техніка може бути поширена на інші ресурси, крім виділення пам'яті. Наприклад, це може бути використано для гарантування того, що файли або з'єднання з базою даних закриваються після використання або що звільняються блокування синхронізації для вашого потокового коду.