Существует два широко используемых метода выделения памяти: автоматическое распределение и динамическое распределение. Обычно для каждой области памяти имеется соответствующая область: стек и куча.
стек
Стек всегда распределяет память последовательно. Это может быть сделано, потому что это требует от вас освобождения памяти в обратном порядке (первый вход, последний выход: 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
экземпляр и все содержимое строки, и все освобождается автоматически .