Існує дві широко використовувані методи розподілу пам'яті: автоматичний розподіл та динамічний розподіл. Зазвичай існує відповідна область пам’яті для кожного: стек і купа.
Стек
Стек завжди виділяє пам'ять послідовно. Це можна зробити, тому що вимагає, щоб ви звільнили пам'ять у зворотному порядку (First-In, Last-Out: FILO). Це методика розподілу пам'яті для локальних змінних у багатьох мовах програмування. Це дуже, дуже швидко, оскільки воно вимагає мінімальної бухгалтерії, а наступна адреса для виділення неявна.
У C ++ це називається автоматичним зберіганням, оскільки зберігання заявляється автоматично в кінці області дії. Як тільки виконання поточного блоку коду (з обмеженим використанням {}
) завершено, пам'ять для всіх змінних у цьому блоці автоматично збирається. Це також момент, коли деструктори викликають для очищення ресурсів.
Купи
Купа забезпечує більш гнучкий режим розподілу пам'яті. Бухгалтерський облік складніший, а розподіл - повільніший. Оскільки немає неявної точки випуску, ви повинні звільнити пам'ять вручну, використовуючи delete
або delete[]
( free
на C). Однак відсутність неявної точки випуску є ключем до гнучкості купи.
Причини використання динамічного розподілу
Навіть якщо використання купи відбувається повільніше і, можливо, призводить до витоку пам’яті або фрагментації пам’яті, є ідеально хороші випадки використання для динамічного розподілу, оскільки воно менш обмежене.
Дві основні причини використання динамічного розподілу:
Ви не знаєте, скільки пам'яті потрібно на час компіляції. Наприклад, читаючи текстовий файл у рядку, ви зазвичай не знаєте, який розмір має файл, тому ви не можете вирішити, скільки пам'яті потрібно виділити до запуску програми.
Ви хочете виділити пам'ять, яка зберігатиметься після виходу з поточного блоку. Наприклад, ви можете написати функцію, string readfile(string path)
яка повертає вміст файлу. У цьому випадку, навіть якщо стек міг вмістити весь вміст файлу, ви не змогли повернутися з функції і зберегти виділений блок пам'яті.
Чому динамічний розподіл часто не потрібен
У C ++ є акуратна конструкція, яка називається деструктором . Цей механізм дозволяє керувати ресурсами, вирівнюючи термін експлуатації ресурсу з терміном служби змінної. Ця методика називається RAII і є відмітною точкою C ++. Він "загортає" ресурси в об'єкти. std::string
є ідеальним прикладом. Цей фрагмент:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
фактично виділяє змінний об'єм пам'яті. std::string
Об'єкт виділяє пам'ять , використовуючи купу і звільняє його в деструкції. У цьому випадку вам не потрібно було керувати будь-якими ресурсами вручну, і все ж отримали переваги динамічного розподілу пам'яті.
Зокрема, це означає, що в цьому фрагменті:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
відбувається непотрібне динамічне розподілення пам'яті. Програма вимагає більшого набору тексту (!) Та вводить ризик забути розмістити пам'ять. Це робиться без видимої вигоди.
Чому слід використовувати автоматичне зберігання якомога частіше
В основному, останній абзац підсумовує це. Використання автоматичного зберігання якомога частіше робить ваші програми:
- швидше набирати;
- швидше при бігу;
- менш схильний до витоку пам'яті / ресурсів.
Бонусні бали
У посиланому питанні є додаткові занепокоєння. Зокрема, наступний клас:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Насправді набагато більш ризиковано використовувати ніж наступне:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
Причина в тому, що std::string
правильно визначається конструктор копій. Розглянемо наступну програму:
int main ()
{
Line l1;
Line l2 = l1;
}
Використовуючи оригінальну версію, ця програма, ймовірно, вийде з ладу, оскільки вона використовується delete
в одній і тій же рядку двічі. Використовуючи модифіковану версію, кожен Line
екземпляр матиме свій власний екземпляр рядка , кожен зі своєю пам'яттю, і обидва будуть випущені в кінці програми.
Інші примітки
Широке використання RAII вважається найкращою практикою застосування C ++ через усі вищевикладені причини. Однак є додаткова вигода, яка не відразу очевидна. В основному, це краще, ніж сума його частин. Весь механізм складається . Це ваги.
Якщо ви використовуєте Line
клас як будівельний блок:
class Table
{
Line borders[4];
};
Тоді
int main ()
{
Table table;
}
виділяє чотири std::string
екземпляри, чотири Line
екземпляри, один Table
екземпляр і весь вміст рядка, і все звільняється автоматично .