Існує важлива різниця між ними.
Все, що не виділено, new
поводиться так само, як типи значень у C # (і люди часто кажуть, що ці об’єкти виділяються на стеку, що, мабуть, є найбільш поширеним / очевидним випадком, але не завжди відповідає дійсності. Точніше, об'єкти, виділені без використання, new
мають автоматичне зберігання тривалість
Все, що виділяється з new
, розподіляється на купі, і вказівник на нього повертається, точно так само, як еталонні типи в C #.
Все, що виділяється на стеку, має мати постійний розмір, визначений під час компіляції (компілятор повинен правильно встановити покажчик стека, або якщо об'єкт є членом іншого класу, він повинен регулювати розмір цього іншого класу) . Ось чому масиви в C # є типовими типами. Вони повинні бути, тому що, використовуючи еталонні типи, ми можемо вирішити під час виконання, скільки пам’яті запитувати. І те саме стосується і тут. Тільки масиви з постійним розміром (розмір, який можна визначити під час компіляції) можуть бути виділені з автоматичною тривалістю зберігання (у стеці). Масиви з динамічним розміром потрібно розподіляти по купі, зателефонувавши new
.
(І на цьому зупиняється будь-яка схожість на C #)
Тепер усе, що виділяється на стеці, має "автоматичну" тривалість зберігання (ви можете фактично оголосити змінну як auto
, але це за замовчуванням, якщо не вказаний інший тип зберігання, тому ключове слово насправді не використовується на практиці, але саме тут він походить від)
Автоматична тривалість зберігання означає саме те, що воно звучить, тривалість змінної обробляється автоматично. Навпаки, все, що виділяється на купі, ви повинні видалити вручну. Ось приклад:
void foo() {
bar b;
bar* b2 = new bar();
}
Ця функція створює три значення, які варто врахувати:
У рядку 1 він оголошує змінну b
типу bar
на стеку (автоматична тривалість).
У другому рядку він оголошує bar
вказівник b2
на стек (автоматична тривалість) і викликає новий, виділяючи bar
об'єкт на купу. (динамічна тривалість)
Коли функція повернеться, відбудеться наступне: По-перше, b2
виходить за межі (порядок руйнування завжди протилежний порядку побудови). Але b2
це лише вказівник, тому нічого не відбувається, пам'ять, яку він займає, просто звільняється. І що важливо, пам'ять, на яку він вказує ( bar
екземпляр на купі) НЕ торкається. Вивільняється лише вказівник, оскільки лише вказівник мав автоматичну тривалість. По-друге, b
виходить за межі, тому оскільки він має автоматичну тривалість, його деструктор викликається, і пам'ять звільняється.
А bar
екземпляр на купі? Це, мабуть, все ще є. Ніхто не заважав видалити його, тому ми просочилися пам’яттю.
З цього прикладу ми бачимо, що все, що має автоматичну тривалість, гарантовано викликає свій деструктор, коли він виходить за межі області. Це корисно. Але все, що виділяється на купі, триває стільки, скільки нам потрібно, і може бути динамічно розміром, як у випадку з масивами. Це теж корисно. Ми можемо використовувати це для управління нашими розподілами пам'яті. Що робити, якщо клас Foo виділив деяку пам’ять на купі у своєму конструкторі та видалив цю пам'ять у своєму деструкторі. Тоді ми могли б отримати найкраще з обох світів, безпечне розподілення пам’яті, яке гарантовано буде звільнене знову, але без обмежень змушувати все бути на стеці.
І саме так працює більшість кодів C ++. Подивіться, наприклад, стандартну бібліотеку std::vector
. Зазвичай виділяється на стеку, але може бути динамічно розміром і розміром. І це робиться, розподіляючи пам’ять на купу, як потрібно. Користувач класу ніколи цього не бачить, тому немає шансів просочити пам'ять або забути очистити те, що ви виділили.
Цей принцип називається RAII (Придбання ресурсів - ініціалізація), і його можна поширити на будь-який ресурс, який необхідно придбати та звільнити. (мережеві розетки, файли, підключення до бази даних, блокування синхронізації). Усі вони можуть бути придбані в конструкторі та випущені в деструктор, тому ви гарантовано знову отримаєте всі звільнені ресурси.
Як правило, ніколи не використовуйте нові / видаляти безпосередньо з коду високого рівня. Завжди загорніть його в клас, який може керувати пам'яттю для вас, і це забезпечить його звільнення знову. (Так, з цього правила можуть бути винятки. Зокрема, розумні покажчики вимагають від вас new
безпосередньо зателефонувати та передати вказівник його конструктору, який потім переймає та забезпечує delete
правильне виклик. Але це все ще дуже важливе правило )